基本情況
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'\) 的段 \([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\),因此答案是
一旦我們找到每種顏色 \(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]\) 中的機會球(任何顏色的)的數量是
因此,要處理的第 \(M\) 個機會球的位置是使得 \(S(l, r) \geq M\) 的最小 \(r\)。記為 \(r_l\),則有 \(b(c, l) = \left\lceil f(c, l, r_l) / K \right\rceil\),因此所求的答案表示為
(如果沒有 \(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;
}