--- tags: Programming Contest --- # ACL Beginner Contest 題解 [題目連結](https://atcoder.jp/contests/abl/tasks) ## 心得 比賽中解出四題,rating +72 \(๑╹◡╹๑)ノ♬ ## A. Repeat ACL ```python K = int(input()) print('ACL' * K) ``` ## B. Integer Preference 檢查區間端點在不在另一個區間之中,反向也要檢查一遍。 ```python A, B, C, D = map(int, input().split()) if C <= A <= D or C <= B <= D or A <= C <= B or A <= D <= B: print('Yes') else: print('No') ``` ## C. Connect Cities 假設整個 graph 會有 g 個彼此不連通的單元,在他們之間建 g - 1 條邊即可使他們連通。g = 1 時,即整個 graph 是連通的,那答案是 0。這題我嘗試用 ACL 的 Disjoint Set 來解。 ```cpp #include <algorithm> #include <atcoder/dsu> #include <iostream> #include <vector> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N, M; cin >> N >> M; auto comps = atcoder::dsu(N); for (int i = 0; i < M; i++) { int u, v; cin >> u >> v; u--; v--; comps.merge(u, v); } int n_comp = comps.groups().size(); int ans = ((n_comp == 1) ? 0 : n_comp - 1); cout << ans << endl; return 0; } ``` ## D. Flat Subsequence 這題讓人想到 LIS,所以我就回憶了一下 LIS $O(nlgn)$ 的解法,改改看。 讓我們從左至右迭代 `A`,同時維護 `dp` 陣列: ``` dp[i] = max length of B where B[-1] = i ``` 這個 dp 陣列的大小即為所有可能的數字`3e5 + 1`,陣列的初使值全為 0。 在 `A[i]` 時,我們可能從哪些數字轉移過來呢?所有位於區間 `[A[i] - K, A[i] + K]` 的數字都可以,於是寫出轉移方程: ``` dp[A[i]] = max([dp[j] for j in range(A[i] - k, A[j] + k + 1)]) + 1 ``` 會發現這樣的 dp 要 $O(NK)$ 才能算完。但觀察可以發現,我們的轉移方程是從 `dp[A[i] - K ... A[i] + K]` 中取最大值出來,再加 1。如果我們**用線段樹來存 dp**,這個操作就可以從 $O(M)$ 變成 $O(lgM)$,也因此整體時間是 $O(NlgM)$,$M$ 是數字的值域大小。 實作上小心 `A[i] - K` 小於 `0` 與 `A[i] + K` 大於等於 `N` 的情況,我因為這個 RE 了一次。 以下給出使用 ACL 的程式碼: ```cpp #include <algorithm> #include <atcoder/segtree> #include <iostream> #include <vector> using namespace std; int op(int a, int b) { return max(a, b); } int default_data() { return 0; } using SegTree = atcoder::segtree<int, op, default_data>; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N, K; cin >> N >> K; vector<int> A(N); for (int i = 0; i < N; i++) { cin >> A[i]; } // dp[i] = the max length of B where B[-1] = i const int M = 300000 + 10; auto dp = SegTree(M); dp.set(A[0], 1); for (int i = 1; i < N; i++) { auto lb = max(A[i] - K, 0); auto ub = min(A[i] + K + 1, M); dp.set(A[i], dp.prod(lb, ub) + 1); } cout << dp.prod(0, M) << endl; return 0; } ``` ## E. Replace Digits 比賽中早早想到是 LazySegTree 但遲遲實作不出來 (。ŏ﹏ŏ)。 今天藉由研究 kort0n 的[程式碼](https://atcoder.jp/contests/abl/submissions/17033157),終於搞懂要怎麼使用 ACL 解決這題了,感謝大大。 存 `54873126` 的線段樹: ![](https://i.imgur.com/zZtVe83.png) 可以想到將 N 位數分解成 N 個值的和,然後用線段樹存,讓線段樹的 root 就是題目所求,如圖 1。 但只存值無法讓我們處理 query。當我們想將區間 `[l, r]` 都改成數字 `d` 時,我們不知道 `[l, r]` 對應的是哪些位數(如果自己刻線段樹的話應該可以,但 ACL 的 interface 不行),所以我們在每個節點除了 `value` 外,另外存一個 `base`,代表該節點 **對應的那些位數的和**,如圖 2。 這樣要將 `[l, r]` 都改成 `d` 時,區間對應的那些節點 `node` 的新 `value` 就會是 `d * node.base`。 實作上要小心 ACL 的 `apply_lazy`, `push_lazy` 在 `lazy` 是預設值時仍然會被呼叫,這跟我的直覺不同,被卡了好久。我們得在函式中判別這種情況。不能讓預設的 lazy (此題是 0)把該點與其子樹的結果給覆蓋掉。 ```cpp #include <atcoder/lazysegtree> #include <atcoder/modint> #include <iostream> #include <vector> using namespace std; using mint = atcoder::modint998244353; struct S { mint value; mint base; }; using F = int; S op(S a, S b) { return {a.value + b.value, a.base + b.base}; } S default_data() { return {0, 0}; } S apply_lazy(F lazy, S data) { if (lazy == 0) { return data; } return {lazy * data.base, data.base}; } F push_lazy(F root, F child) { if (root == 0) { return child; } return root; } F default_lazy() { return 0; } using SegTree = atcoder::lazy_segtree<S, op, default_data, F, apply_lazy, push_lazy, default_lazy>; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N, Q; cin >> N >> Q; auto leaves = vector<S>(N); mint base = 1; for (int i = 0; i < N; i++) { leaves[i] = {base, base}; base = base * 10; } auto tree = SegTree(leaves); while (Q--) { int l, r, d; cin >> l >> r >> d; l--; r--; tree.apply(N - 1 - r, N - l, d); cout << tree.all_prod().value.val() << "\n"; } return 0; } ``` :::success [< 回到所有題解](https://hackmd.io/@amoshyc/HkWWepaPv/) :::