AtCoder Beginner Contest 368

~Lanly~發表於2024-08-24

A - Cut (abc368 A)

題目大意

給定一個陣列,將右邊\(k\)個數保持原順序挪到左邊,輸出。

解題思路

即從左邊第\(n-k\)個數開始輸出即可。或者用rotate函式轉換一下。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, k;
    cin >> n >> k;
    k = n - k;
    vector<int> a(n);
    for (auto& i : a)
        cin >> i;
    rotate(a.begin(), a.begin() + k, a.end());
    for (auto i : a)
        cout << i << ' ';
    cout << '\n';

    return 0;
}



B - Decrease 2 max elements (abc368 B)

題目大意

給定一個陣列,每次將最大的兩個數減一,直到只有一個或無正數。輸出操作的次數。

解題思路

最多\(100\)個數,每個數最大 \(100\),因此最大的操作次數只有\(O(100^2)\),加上每次操作的複雜度是 \(O(n\log n)\),因此直接模擬的複雜度即為 \(O(n^3\log n)\),能過。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<int> a(n);
    for (auto& i : a)
        cin >> i;
    int cnt = 0;
    while (count_if(a.begin(), a.end(), [](int x) { return x > 0; }) > 1) {
        sort(a.begin(), a.end(), greater<int>());
        a[0]--;
        a[1]--;
        cnt++;
    }
    cout << cnt << '\n';

    return 0;
}



C - Triple Attack (abc368 C)

題目大意

\(n\)個怪獸血量 \(h_i\),依次打它們。當前怪物死了則打下一個。

初始時刻\(t=0\),每時刻都會對當前怪物造成 \(1\)的傷害,如果 \(t \% 3 == 0\),則可以額外造成 \(2\)的傷害(即一共 \(3\)的傷害)。

問打死所有怪物時的時刻。

解題思路

直接模擬時刻流逝,其最大時刻高達\(O(10^{14})\),但注意到打怪物的時刻越多,傷害越高,因此對於每個怪獸,我們不必模擬時刻流逝,而是二分打死怪物的時刻 \(T\),看看時間\((t, T]\)的傷害是否足夠打死當前怪獸。

對每個怪獸都二分求出一下打死的時刻,最後的二分結果即為答案。

計算 \((t, T]\)時間的傷害,即為 \(T - t + \lfloor \frac{T}{3} \rfloor - \lfloor \frac{t}{3} \rfloor\)

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    LL t = 0;
    LL up = 1e18;
    auto check = [](LL l, LL r) -> LL { return r - l + 2ll * (r / 3 - l / 3); };
    auto solve = [&](LL t, int x) -> LL {
        LL l = t, r = up;
        while (l + 1 < r) {
            LL mid = (l + r) / 2;
            if (check(t, mid) < x)
                l = mid;
            else
                r = mid;
        }
        return r;
    };
    while (n--) {
        int x;
        cin >> x;
        t = solve(t, x);
    }
    cout << t << '\n';

    return 0;
}



D - Minimum Steiner Tree (abc368 D)

題目大意

給定一棵樹,問保留\(k\)個點且連通的最小點數是多少。

解題思路

以第一個保留的點為根,進行\(DFS\)

\(DFS\)過程中的當前點 \(u\),判斷其是否需要保留,則需要知道其子樹是否有需要保留的點,有則當前點 \(u\)需要保留。

因此 \(DFS\)返回的東西即為該子樹是否有需要保留的點\(fixed\)。最後答案就是 fixed點的數量。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, k;
    cin >> n >> k;
    vector<vector<int>> edge(n);
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        u--;
        v--;
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    vector<int> fix(n, 0);
    int root = 0;
    for (int i = 0; i < k; ++i) {
        cin >> root;
        --root;
        fix[root] = 1;
    }
    int ans = 0;
    auto dfs = [&](auto dfs, int u, int fa) -> bool {
        bool fixed = fix[u];
        for (auto v : edge[u]) {
            if (v == fa)
                continue;
            fixed |= dfs(dfs, v, u);
        }
        ans += fixed;
        return fixed;
    };
    dfs(dfs, root, root);
    cout << ans << '\n';

    return 0;
}



E - Train Delay (abc368 E)

題目大意

給定\(n\)個城市和 \(m\)個火車的資訊,從\(s_i \to t_i\),時間是 \(S_i \to T_i\)

先給定 \(x_1\),即第一個城市火車延誤出發時間,要求 \(x_2,x_3,...,x_n\),使得:

  • 對於任意兩個火車資訊滿足 \(t_i = s_j, T_i \leq S_j\)的,也滿足\(T_i + x_i \leq S_j + x_j\),即原來可換乘的倆列車, 在延誤後仍可換乘。
  • \(\sum_{i=2}^n x_i\)最小

解題思路

<++>

神奇的程式碼



F - Dividing Game (abc368 F)

題目大意

給定\(n\)個數, \(Anna\)杏菜\(Bruno\)玩遊戲, \(Anna\)先。

每輪,選一個數,將其變為真因子。

無法操作即輸。問最優情況下誰贏。

解題思路

知道博弈論的\(SG\)知識就可以解了。

每個數之間是互不干擾的,因此每個數可以視為一個獨立局面,因此整個局面的\(sg\)值就是每個獨立局面的 \(sg\)值的異或。

考慮一個獨立局面的 \(sg\)值怎麼求,即 \(sg[x]\)。根據 \(sg\)值定義,其為所有後繼情況的 \(sg\)值的 \(mex\),即 \(sg[x] = \mex_{x \% i == 0} (sg[i])\)

列舉因子數是\(O(\sqrt{x}) = 10^{2.5}\),而\(n=10^5\) ,因此花\(O(n\sqrt{x})\)預處理出所有數的\(sg\)值,然後異或一下,為 \(0\)則 先手輸,否則 先手贏。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int N = 1e5 + 8;
    vector<int> sg(N);
    sg[1] = 0;
    auto getSG = [&](int x) {
        set<int> hold{sg[1]};
        for (int i = 2; i * i <= x; ++i) {
            if (x % i == 0) {
                hold.insert(sg[x / i]);
                hold.insert(sg[i]);
            }
        }
        for (int i = 0; i < N; ++i) {
            if (hold.find(i) == hold.end()) {
                return i;
            }
        }
        assert(0);
    };
    for (int i = 2; i < N; ++i) {
        sg[i] = getSG(i);
    }
    int n;
    cin >> n;
    int ans = 0;
    for (int i = 0; i < n; ++i) {
        int x;
        cin >> x;
        ans ^= sg[x];
    }
    if (ans) {
        cout << "Anna" << '\n';
    } else {
        cout << "Bruno" << '\n';
    }

    return 0;
}



G - Add and Multiply Queries (abc368 G)

題目大意

給定兩個陣列\(a,b\),維護下列三種操作:

  • 1 i x,令\(a_i = x\)
  • 2 i x,令\(b_i = x\)
  • 3 l r,求最大值\(v\),其初始 \(v=0\),依次 \(i \in [l,r]\) ,令\(v = v + a_i\)\(v = v * b_i\)

題目保證操作三的答案不超過\(10^{18}\)

解題思路

對於一次操作\(3\),很顯然對於 \(i \in [l,r]\) ,我令\(v = \max(v + a_i, v * b_i)\)。一次的複雜度是 \(O(n)\)

注意到操作三的答案不超過\(10^{18}\),這意味著什麼呢。

首先,如果 \(b_i = 1\),我們很顯然是選擇操作 \(1\),而對於要判斷最大值的,必定有 \(b_i \geq 2\)\(v = \max(v + a_i, v*b_i) \geq 2v\),也就是說,每做一次判斷, \(v\)都會翻倍,最多翻 \(64\)次,就達到 \(10^{18}\)了。

所以,每次區間 \([l,r]\)中,實際上只有最多\(O(64)\)\(b_i \geq 2\),其餘都是 \(1\),這意味著都會選擇 \(v=v+a_i\)

因此每次操作區間 \([l,r]\),我們直接模擬,

  • 找到第一個 \(b_{pos} \geq 2\)
  • 處理 \([l, pos-1]\)的操作,即 \(v = v + suma[l..pos-1]\)
  • 處理 \([pos, pos]\)的操作,即 \(v = \max(v + a_{pos}, v * b_{pos})\)
  • \(l = pos + 1\),重複第一個操作

最多跑 \(O(60)\)次就跑完了,因為帶修改,第一步操作就用線段樹上二分,透過區間最大值>1找到其位置,第二步操作就是一個區間和。

神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

const int N = 2e5 + 8;

class info {
  public:
    LL sum;
    int maxx;

    info(LL sum = 0, int maxx = 0) : sum(sum), maxx(maxx) {}
    info operator+(const info& rhs) const {
        return info(sum + rhs.sum, max(maxx, rhs.maxx));
    }
};

class segment {
#define lson (root << 1)
#define rson (root << 1 | 1)
  public:
    info val[N << 2];

    void build(int root, int l, int r, vector<int>& a, vector<int>& b) {
        if (l == r) {
            val[root] = info(a[l], b[l]);
            return;
        }
        int mid = (l + r) >> 1;
        build(lson, l, mid, a, b);
        build(rson, mid + 1, r, a, b);
        val[root] = val[lson] + val[rson];
    }

    void update(int root, int l, int r, int pos, info& v) {
        if (l == r) {
            val[root] = v;
            return;
        }
        int mid = (l + r) >> 1;
        if (pos <= mid)
            update(lson, l, mid, pos, v);
        else
            update(rson, mid + 1, r, pos, v);
        val[root] = val[lson] + val[rson];
    }

    info query(int root, int l, int r, int L, int R) {
        if (L <= l && r <= R) {
            return val[root];
        }
        int mid = (l + r) >> 1;
        info resl{}, resr{};
        if (L <= mid)
            resl = query(lson, l, mid, L, R);
        if (R > mid)
            resr = query(rson, mid + 1, r, L, R);
        return resl + resr;
    }

    int findfirst(int root, int l, int r, int L, int R) {
        if (l > R || r < L) {
            return 0;
        }
        if (L <= l && r <= R && val[root].maxx < 2) {
            return 0;
        }
        if (l == r) {
            return l;
        }
        int mid = (l + r) >> 1;
        int ret = findfirst(lson, l, mid, L, R);
        if (ret == 0) {
            ret = findfirst(rson, mid + 1, r, L, R);
        }
        return ret;
    }
} seg;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    vector<int> b(n + 1);
    for (int i = 1; i <= n; ++i)
        cin >> b[i];
    seg.build(1, 1, n, a, b);
    int q;
    cin >> q;
    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            int pos, x;
            cin >> pos >> x;
            a[pos] = x;
            info v(a[pos], b[pos]);
            seg.update(1, 1, n, pos, v);
        } else if (op == 2) {
            int pos, x;
            cin >> pos >> x;
            b[pos] = x;
            info v(a[pos], b[pos]);
            seg.update(1, 1, n, pos, v);
        } else {
            int l, r;
            cin >> l >> r;
            LL ans = a[l];
            l++;
            while (l <= r) {
                int pos = seg.findfirst(1, 1, n, l, r);
                if (pos == 0) {
                    ans += seg.query(1, 1, n, l, r).sum;
                    break;
                } else {
                    info v = seg.query(1, 1, n, l, pos - 1);
                    ans += v.sum;
                    ans = max(ans + a[pos], ans * b[pos]);
                    l = pos + 1;
                }
            }
            cout << ans << '\n';
        }
    }

    return 0;
}



相關文章