AtCoder Beginner Contest 376

~Lanly~發表於2024-10-19
省流版
  • A. 記錄上次發糖時間,依題意模擬判斷即可
  • B. 移動策略唯一,模擬移動即可
  • C. 二分箱子大小,從小到大依次判斷能否放入即可
  • D. 分別從點\(1\)透過正反向邊到達點\(i\)的距離最小值,正反邊分別\(BFS\)求距離最小值即可。
  • E. 列舉\(\max a_i\),用優先佇列維護滿足 \(a_j \leq a_i\) 的前\(b_j\)小的和即可
  • F. 注意每執行完一條指令後只有一個棋子情況數有\(O(n)\),因此直接 \(dp[i][j]\)表示執行完前 \(i\)條指令,另一個棋子位於 \(j\)的最小移動次數,根據前後兩個指令移動的棋子是否相同,轉移分別考慮順指標移動或逆時針移動對另一個棋子的位置影響和代價即可

A - Candy Button (abc376 A)

題目大意

一個按鈕,按了會發糖。

給定多次按的時間。如果這次按的時間距離上次發糖時間超過了\(c\),則發個糖。

問發的糖數量。

解題思路

記錄上次發糖的時間,然後判斷此次按按鈕的時間是否和上次距離超過\(c\)即可。

神奇的程式碼
#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, c;
    cin >> n >> c;
    int la = -c;
    int ans = 0;
    while (n--) {
        int x;
        cin >> x;
        if (x - la >= c) {
            ans++;
            la = x;
        }
    }
    cout << ans << '\n';

    return 0;
}



B - Hands on Ring (Easy) (abc376 B)

題目大意

\(n\)的環形格子。兩個棋子,初始位於\(0,1\)

給定 \(q\)個指令,每個指令指定一個棋子移動到某個格子上,期間不能移動另外一個棋子。

依次執行這些指令,問移動的次數。

解題思路

模擬即可,一個棋子只能順時針逆時針移動,其中一個方向會撞到另一個棋子而不能移動,因此每次只有一個方向可以移動。

直接模擬兩次移動,其時間複雜度為\(O(n^2q)\),同樣可過。或者移動前判斷某個方向會不會撞到另一個棋子,其時間複雜度為\(O(nq)\)

程式碼裡判斷的是順時針是否會遇到另一個棋子\(b\)。雖然有環會導致判斷困難,但可以這麼想:

因為是順時針,格子標號不斷遞增,因此保持\(s < t\)(如果\(t < s\)則令 \(t = t + n\)),然後看 \(b\)或者 \(b-n\)或者 \(b+n\)是否在 \([s,t]\)中間,即可知道\(s \to t\)會經過\(b\)

神奇的程式碼
#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, q;
    cin >> n >> q;
    int l = 0, r = 1;
    int ans = 0;
    auto inner = [](int l, int m, int r) { return l < m && m < r; };
    auto step = [&](int s, int t, int b) {
        if (s == t)
            return 0;
        if (s > t)
            t += n;
        if (inner(s, b, t) || inner(s, b + n, t))
            return n - (t - s);
        else
            return t - s;
    };
    while (q--) {
        string s;
        int p;
        cin >> s >> p;
        --p;
        if (s[0] == 'L') {
            ans += step(l, p, r);
            l = p;
        } else {
            ans += step(r, p, l);
            r = p;
        }
    }
    cout << ans << '\n';

    return 0;
}



C - Prepare Another Box (abc376 C)

題目大意

給定\(n\)個球的大小和 \(n-1\)個箱子的大小。現買一箱子,要求尺寸最小,使得 \(n\)個球恰好可以放進 \(n\)個箱子裡。

解題思路

容易發現,如果我們已知了最後一個箱子的尺寸,我們很容易判斷能否恰好放進:對球和箱子的尺寸從小到大排序,然後看是否一一放進去即可。

但此處就是要求其尺寸的最小值,可以列舉,但很顯然,最後一個箱子越大,可以放進它的球越多,要求越容易滿足,反之越小的時候,越難滿足。所以箱子尺寸是否可行具有單調性。

因此二分最後一個箱子的尺寸,然後\(O(n\log n)\)判斷是否可行即可。

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

const int inf = 1e9 + 7;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<int> a(n), b(n);
    for (auto& i : a)
        cin >> i;
    for (int i = 0; i < n - 1; ++i)
        cin >> b[i];
    int l = 0, r = inf;
    sort(a.begin(), a.end());
    auto check = [&](int x) {
        auto bb = b;
        bb.back() = x;
        sort(bb.begin(), bb.end());
        for (int i = 0; i < n; ++i)
            if (a[i] > bb[i])
                return false;
        return true;
    };
    while (l + 1 < r) { // (l, r]
        int mid = (l + r) >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid;
    }
    if (r == inf)
        r = -1;
    cout << r << '\n';

    return 0;
}



D - Cycle (abc376 D)

題目大意

給定一張有向圖,問包含點\(1\)的環的最小環點數。

解題思路

想的時候腦子有點不清醒,其經歷為:

  • 直接從點\(1\) \(DFS\),發現 \(T\)了,思索下發現是環套環的情況會退化成 \(O(n^2)\)
  • 退化的原因是一個點被重複訪問了,避免重複訪問,記錄了 \(dis[i]\)表示第一次從點 \(i\)回到點 \(1\)的距離,發現 \(wa\)了,思索下 發現是第一次記錄的值不一定是最小值
  • 要確保最小值,只好改成\(BFS\),但其 \(dis[i]\)\(1 \to i\)的,我還需要一個 \(dis[i] \to 1\)的,發現該距離就是反向邊的結果,因此就正反方向從\(1\)開始 各\(BFS\)一次即可。

包含點\(1\)的環可以看成是 從點\(1\)出發,正向邊和反向邊到達同一個點所組成的路徑。

因此從點 \(1\)分別沿正向邊和反向邊求出 \(dis[i]\)\(dis2[i]\)表示 \(1 \to i\)的正向邊和反向邊長度 ,答案就是\(\min_i dis[i] + dis2[i]\)

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

const int inf = 1e9 + 7;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<vector<int>> edge(n);
    vector<vector<int>> edge2(n);
    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        --u, --v;
        edge[u].push_back(v);
        edge2[v].push_back(u);
    }
    int ans = inf;
    int cnt = 0;
    auto BFS = [&](int st, vector<vector<int>>& e) {
        queue<int> team;
        vector<int> dis(n, inf);
        dis[st] = 0;
        team.push(st);
        while (!team.empty()) {
            auto u = team.front();
            team.pop();
            for (auto& v : e[u]) {
                if (dis[v] > dis[u] + 1) {
                    team.push(v);
                    dis[v] = dis[u] + 1;
                }
            }
        }
        return dis;
    };
    auto dis = BFS(0, edge);
    auto dis2 = BFS(0, edge2);
    for (int i = 1; i < n; ++i) {
        ans = min(ans, dis[i] + dis2[i]);
    }
    if (ans == inf)
        ans = -1;

    cout << ans << '\n';

    return 0;
}



E - Max × Sum (abc376 E)

題目大意

給定兩個陣列\(a,b\),求一個大小為 \(k\)的集合下表 \(S\),使得 \(\max_{i \in S} a_i + \sum_{i \in S} b_i\)最小。

解題思路

列舉\(a_i\),剩下的問題就是找滿足 \(a_j \leq a_i\)的前 \(k-1\)小的 \(b_j\)的和。

首先對陣列\(a\)從小到大排序,陣列 \(b\)也跟著變動。

然後依次列舉 \(a_i\),此即為\(\max_{i \in S} a_i\)。然後找\(1 \leq j \leq i\)中最小的 \(k-1\)\(b_i\)

考慮如何維護前 \(k-1\)小的和,因為會有不斷的 \(b_i\)加入,會不斷淘汰較大的 \(b_i\),因此可以用優先佇列維護這些 \(b_i\)

在優先佇列不斷 push,pop時維護其裡面的值的即可。其時間複雜度為\(O(n\log n)\)

注意列舉 \(a_i\)時, \(b_i\)是一定要選的,因此要從優先佇列裡求出前 \(k-1\)小的和(但第 \(k\)小的不能丟棄,它可能比 \(b_i\)小,只是因為此時 \(b_i\)必選,因而暫時不能選它)。

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

const LL inf = 1e18 + 7;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while (t--) {
        int n, k;
        cin >> n >> k;
        vector<int> a(n), b(n), id(n);
        for (auto& i : a)
            cin >> i;
        for (auto& i : b)
            cin >> i;
        iota(id.begin(), id.end(), 0);
        sort(id.begin(), id.end(), [&](int x, int y) { return a[x] < a[y]; });
        priority_queue<int> pq;
        LL sum = 0;
        LL ans = inf;
        for (int j = 0; j < n; ++j) {
            int i = id[j];
            while (pq.size() > k) {
                sum -= pq.top();
                pq.pop();
            }
            if (j >= k - 1) {
                int del = (j >= k ? pq.top() : 0);
                ans = min(ans, 1ll * a[i] * (sum - del + b[i]));
            }
            pq.push(b[i]);
            sum += b[i];
        }
        cout << ans << '\n';
    }

    return 0;
}



F - Hands on Ring (Hard) (abc376 F)

題目大意

\(n\)的環形格子。兩個棋子,初始位於\(0,1\)

給定 \(q\)個指令,每個指令指定一個棋子移動到某個格子上,期間可以移動另外一個棋子。

依次執行這些指令,問最小的移動次數。

解題思路

樸素\(dp\)即為 \(dp[i][j][k]\)表示執行了前 \(i\)個指令後,兩個棋子的位置分別為\(j,k\)的最小移動次數。

但其時間複雜度為 \(O(qn^2)\),由於 \(n,q \leq 3000\),不能透過。

但注意到,當執行完第\(i\)個指令時,其中一個棋子的位置是一定是剛剛移動到的位置(情況數為\(1\)),只是另一個棋子的位置 不確定,情況數為\(O(n)\)。因此實際上狀態數就只有 \(O(qn)\),轉移可以 \(O(1)\),因此可以透過了。只是實現有一點點思維細節。

\(dp[i][j]\)表示執行完前 \(i\)個指令後,另一個棋子(如果第\(i\)條指令移動的是 L,則另一個棋子為R,反之為L)位於 \(j\)時的最小移動次數。

然後\(dp[i] \to dp[i + 1]\)時,設第\(i\)個指令移動的位置是 \(lp\),第 \(i+1\)個指令移動的位置是 \(p\) ,考慮第\(i\)個指令和第 \(i+1\)個指令移動的是否是同一個棋子。

如果是同一個棋子:

  • 在第\(i\)個指令時,當前移動棋子\(a\),另一個棋子 \(b\)(即\(j\)位置所指的棋子)。
  • 在第\(i+1\)個指令時,當前移動棋子\(a\),另一個棋子 \(b\)(即\(j\)位置所指的棋子)。
  • 此時計算\(dp[i+1]\),列舉\(dp[i][j]\)\(j\),這是棋子\(b\)的位置。考慮轉移,即 棋子\(a\)\(lp \to p\),轉移方式有兩種:順時針和逆時針,分別考慮這兩個方向的代價和對棋子 \(b\)位置\(j\)的影響,轉移即可。

如果不是同一個棋子:

  • 在第\(i\)個指令時,當前移動棋子\(b\),另一個棋子 \(a\)(即\(j\)位置所指的棋子)。
  • 在第\(i+1\)個指令時,當前移動棋子\(a\),另一個棋子 \(b\)(即\(j\)位置所指的棋子)。
  • 此時計算\(dp[i+1]\),列舉\(dp[i][j]\)\(j\),這是棋子\(a\)的位置。考慮轉移,即 棋子\(a\)\(j \to p\),轉移方式有兩種:順時針和逆時針,分別考慮這兩個方向的代價和對棋子 \(b\)位置的\(lp\)影響,轉移即可。

對於轉移方式的計算,就是三個引數\(s \to t\),對 \(b\)的影響,返回棋子 \(b\)的位置和總代價。

  • 順時針 \(s \to t\),位置標號不斷增大,因此若 \(t < s\),則令 \(t += n\),然後看 \(b - n, b, b + n\)是否有一個落在 \([s, t]\)範圍內,有則最終 \(b\)的位置會變成 \(t+1\)。 然後計算兩個棋子移動的距離即可。注意計算其距離時要將\(b\)修正在 \([s,t]\)的範圍內。
  • 逆時針 \(s \to t\),位置標號不斷減小,因此若 \(t > s\),則令 \(t -= n\),然後看 \(b - n, b, b + n\)是否有一個落在 \([t, s]\)範圍內,有則最終 \(b\)的位置會變成 \(t-1\)。 然後計算兩個棋子移動的距離即可。注意計算其距離時要將\(b\)修正在 \([t,s]\)的範圍內。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

const int inf = 1e9 + 7;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, q;
    cin >> n >> q;
    vector<int> dp(n, inf);
    char cur = 'L';
    int lp = 0;
    dp[1] = 0;
    auto inner = [](int l, int m, int r) { return l <= m && m <= r; };
    auto shun = [&](int s, int t, int b) {
        if (s == t)
            return make_pair(b, 0);
        if (s > t)
            t += n;
        if (inner(s, b - n, t) || inner(s, b, t) || inner(s, b + n, t)) {
            if (inner(s, b - n, t))
                b -= n;
            else if (inner(s, b + n, t))
                b += n;
            int cost = t - s + t + 1 - b;
            int pos = (t + 1) % n;
            return make_pair(pos, cost);
        } else {
            return make_pair(b, t - s);
        }
    };
    auto ni = [&](int s, int t, int b) {
        if (s == t)
            return make_pair(b, 0);
        if (s < t)
            t -= n;
        if (inner(t, b - n, s) || inner(t, b, s) || inner(t, b + n, s)) {
            if (inner(t, b - n, s))
                b -= n;
            else if (inner(t, b + n, s))
                b += n;
            int cost = s - t + b - (t - 1);
            int pos = (t - 1 + n) % n;
            return make_pair(pos, cost);
        } else {
            return make_pair(b, s - t);
        }
    };
    while (q--) {
        string s;
        int p;
        cin >> s >> p;
        --p;
        vector<int> dp2(n, inf);
        if (s[0] == cur) {
            for (int i = 0; i < n; ++i) {
                auto [nxt, cost] = shun(lp, p, i);
                dp2[nxt] = min(dp2[nxt], dp[i] + cost);
                auto [nxt2, cost2] = ni(lp, p, i);
                dp2[nxt2] = min(dp2[nxt2], dp[i] + cost2);
            }
        } else {
            for (int i = 0; i < n; ++i) {
                auto [nxt, cost] = shun(i, p, lp);
                dp2[nxt] = min(dp2[nxt], dp[i] + cost);
                auto [nxt2, cost2] = ni(i, p, lp);
                dp2[nxt2] = min(dp2[nxt2], dp[i] + cost2);
            }
        }
        cur = s[0];
        lp = p;
        dp2.swap(dp);
    }
    cout << *min_element(dp.begin(), dp.end()) << '\n';

    return 0;
}



G - Treasure Hunting (abc376 G)

題目大意

給定一棵\(n+1\)個點的有根樹。根為\(0\)號點。

\(1 \sim n\)個點中有一個點有寶藏,給定 \(a_i\),表示第 \(i\)個點有寶藏的機率為 \(\frac{a_i}{\sum a_i}\)

點有已探索未探索兩個狀態。初始點\(0\)已探索,其餘點為未探索

每次,可選擇一個未探索的點,且其父親為已探索的點,探索該點。一旦探到寶藏即停下。

由於寶箱位置不固定,求一個最佳策略,該策略在所有情況的探索點的期望值最小。

即求策略\(f\),記 \(f(x)\)表示如果寶箱位於點 \(x\),該策略需要探索 \(f(x)\)個點才能探到位於點 \(x\)的寶箱。

最小化 \(\sum_{1 \leq i \leq n}\frac{a_i}{\sum a_i} f(i)\)

解題思路

<++>

神奇的程式碼