---
# System prepended metadata

title: ACL Beginner Contest 題解
tags: [Programming Contest]

---

---
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/)
:::