ABC337

加固文明幻景發表於2024-05-22

基本情況

ABC 秒了,D 陣列在空間複雜度上面第一次瘋狂吃虧,吃了兩次罰時過。

賽後看官方題解,發現C做法薄紗我。

C - Lining Up 2

https://atcoder.jp/contests/abc337/tasks/abc337_c

這題一眼連結串列,我用雙向連結串列實現,程式碼石山。

官方題解就題論題,更本質。

其實這題並沒必要開雙向連結串列,因為實際上只有一種位置關係,左到右。

直接維護一個類似單連結串列的資料結構就行了。

#include <iostream>
#include <vector>

int main() {
    using namespace std;
    unsigned N;
    cin >> N;

    vector<unsigned> B(N + 1, N + 1); // B[i] 儲存下標對應的人的右邊的人
    unsigned front;// 隊頭

    for(unsigned i = 1; i <= N; ++i){
        int A;
        cin >> A; 
        if(A < 0)
            front = i;//維護隊頭
        else
            B[A] = i; //更新A右邊的人
    }

    while(front <= N){ // 就類似鄰接表遍歷,從隊頭開始
        cout << front << " ";
        front = B[front];
    }
    cout << endl;
    return 0;
}

E - Bad Juice

https://atcoder.jp/contests/abc337/tasks/abc337_e

看似是互動,實則是結合bitmask的構造題。

因為每個人是否拉肚子就 \(0,1\) 兩種情況,所以可以按位構造。

對於第 \(i\) 個人,讓他喝完編號第 \(i\)\(1\) 的所有果汁。

如果第 \(i\) 個人中毒了,那麼第 \(i\) 位為 \(1\) 的果汁肯定有問題,這裡每個 \(i\) 中毒所表示的資訊是不互相沖突的,可以疊加的:

如果第 \(3,4,6,7\) 個人中毒了,那麼就說明有問題的果汁其 \(3,4,6,7\) 位都為 \(1\)

這樣只要最多拉 \(m,(n \leq 2^m)\) 個人就能涵蓋所有狀況,可以證明拉的人最少。

把所有 \(i\) 中毒的位全部或上去,答案就是中毒的果汁。

signed main() {

    std::cin.tie(nullptr)->sync_with_stdio(false);

    int n;
    std::cin >> n;

    int m = 0;
    while ((1 << m) < n) {m += 1;}
    std::cout << m << std::endl;

    for (int i = 0; i < m; i++) {//選n個人品嚐
        std::vector<int> res;
        for (int j = 0; j < n; j++) if (j & (1 << i)) {//如果j果汁第i位是1,就讓i品嚐
            res.push_back(j + 1);
        }
        std::cout << sz(res) << ' ';
        for (auto& x : res) {std::cout << x << ' ';}
        std::cout << std::endl;
    }

    std::string s;
    std::cin >> s;

    int ans = 0;

    for (int i = 0; i < m; i++) if (s[i] == '1') {
        ans |= (1 << i);//有毒的果汁第i位肯定為1
    }

    std::cout << (ans + 1) << std::endl;

    return 0;
}

F - Usual Color Ball Problems

https://atcoder.jp/contests/abc337/tasks/abc337_f

超級噁心的滑動視窗。

為了方便起見,我們考慮序列

\[C' = (C'_1, C'_2, \ldots, C'_{2N}) \coloneqq (C_1, C_2, \ldots, C_{N}, C_1, C_2, \ldots, C_{N}), \]

這個序列是透過連線兩個給定序列的副本得到的,並且將此問題看作是要找到針對 \(C'\) 的段 \([l, l+N-1]\) 的操作中盒子中的球的總數,對於每個 \(l = 1, 2, \ldots, N\)。令 \(f(c, l, r)\) 表示段 \([l, r]\) 中顏色為 \(c\) 的球的數量,\(g(c)\) 表示整個原始序列 \(C\) 中顏色為 \(c\) 的球的數量。

首先,我們考慮如何找到固定段 \([l, l+N-1]\) 的答案。作為針對段 \([l, l+N-1]\) 的操作的結果,如果用於儲存顏色為 \(c\) 的球的箱子數量為 \(b(c, l)\),那麼結果中顏色為 \(c\) 的球的數量是 \(\min\lbrace b(c, l) \times K, g(c)\rbrace\),因此答案是

\[\mathrm{ans}_l = \sum_{c = 1}^N \min\lbrace b(c, l) \times K, g(c)\rbrace. \]

一旦我們找到每種顏色 \(c\)\(b(c, l)\),我們也可以找到 \(\mathrm{ans}_l\),因此我們接下來考慮如何找到它。

如果一個球被放入一個空箱子中(這增加了用於該顏色的箱子的數量),如果這個球是要處理的顏色的第 \(1\) 個、\((K+1)\) 個、\((2K+1)\) 個、\((3K+1)\) 個等球,則會消耗一個空箱子。因此,讓我們稱每種顏色的第 \(1\) 個、\((K+1)\) 個、\((2K+1)\) 個、\((3K+1)\) 個等球為機會球

每次處理一個機會球(任何顏色的)時,都會消耗一個空箱子,因此 \(b(c, l)\) 等於前 \(M\) 個要處理的機會球中顏色為 \(c\) 的球的數量。因此,要處理的第 \(M\) 個機會球的位置是什麼?

當從位置 \(l\) 開始操作時,段 \([l, r]\) 中顏色為 \(c\) 的機會球的數量是 \(\left\lceil f(c, l, r) / K \right\rceil\),因此段 \([l, r]\) 中的機會球(任何顏色的)的數量是

\[S(l, r) \coloneqq \sum_{c = 1}^N \left\lceil \frac{f(c, l, r)}{K} \right\rceil. \]

因此,要處理的第 \(M\) 個機會球的位置是使得 \(S(l, r) \geq M\) 的最小 \(r\)。記為 \(r_l\),則有 \(b(c, l) = \left\lceil f(c, l, r_l) / K \right\rceil\),因此所求的答案表示為

\[ \mathrm{ans}_l = \sum_{c = 1}^N \min\left\lbrace \left\lceil \frac{f(c, l, r_l)}{K} \right\rceil \times K, g(c) \right\rbrace. \tag{1} \]

(如果沒有 \(r \leq l+N-1\) 滿足 \(S(l, r) \geq M\),則為了方便起見,我們讓 \(r_l \coloneqq l+N-1\)。)因此,為了找到 \(\mathrm{ans}_l\),只需要為固定的 \(l\) 找到 \(r_l\),並評估上述式子 (1)。然而,簡單地對所有 \(l = 1, 2, \ldots, N\) 進行計算是不可能在執行時間限制內完成的。

相反,注意到根據上面的討論,\(r_1 \leq r_2 \leq \cdots \leq r_N\)。為了按順序評估 \(l = 1, 2, \ldots, N\) 的答案,\(r_l\) 可以以滑動視窗的方式高效地找到,而滑動視窗時,還要保持對當前段 \([l', r']\) 對應的值 (1),並且每次 \(l'\)\(r'\) 增加一個時應用增量更新,以便以總共 \(O(N)\) 的時間找到 \(\mathrm{ans}_l\)

signed main() {

    std::cin.tie(nullptr)->sync_with_stdio(false);

    int n, m, k;
    std::cin >> n >> m >> k;
    std::vector<int> c(n * 2);
    for (int i = 0; i < n; i++) {std::cin >> c[i]; --c[i]; c[i + n] = c[i];}//破環成鏈
    std::vector<int> all(n);//維護該顏色在1~n的出現次數
    for (int i = 0; i < n; i++) {all[c[i]] += 1;}
    std::vector<int> f(n);//即f(c, l, r),[l, r]區間內顏色為c的球的出現數量
    std::vector<int> box(n);//box[c]表示顏色為c的盒子的數量

    i64 ans = 0, totBox = 0;

    auto add = [&](int color, int x) {
        //消除沒加之前的改顏色球對答案的影響
        ans -= std::min(box[color] * k, all[color]);
        totBox -= box[color];
        box[color] -= (f[color] + k - 1) / k;
        f[color] += x;//[l, r]區間該顏色球的數量+1
        //處理加之後的影響
        box[color] += (f[color] + k - 1) / k;//用的盒子是總數 / k向上取整
        totBox += box[color];//盒子數量加上去
        ans += std::min(box[color] * k, all[color]);//更新答案
    };

    for (int l = 0, r = 0; l < n; l++) {
        while (r < l + n and totBox < m) {add(c[r], 1); r++;}//如果盒子還沒用完並且右端點也還沒到頭
        std::cout << ans << '\n';
        add(c[l], -1);//剪掉開頭
    }

    return 0;
}