---
# System prepended metadata

title: AtCoder Beginner Contest 174 題解
tags: [Programming Contest]

---

---
tags: Programming Contest
---

# AtCoder Beginner Contest 174 題解

[題目連結](https://atcoder.jp/contests/abc174/tasks)

## 心得

解五題，排 1387

## A, B

略

## C. Repsept

序列的數字可能非常大，該怎麼高速地判定某大數是不是 K 的倍數呢？Horner's Rule！這個技巧真常出現。

同時序列的每一項是其前一項乘 10 再加 7，這就可以跟 Horner's Rule 結合在一起，所以我們可以高速的迭代序列的每項，同時判斷各項是不是 K 的倍數。問題是該最多該迭代幾項，都沒找到就判定不存在？比賽時我直接猜是 K 項，沒仔細思考，然後就過了。

賽後同學分析：因為是對 K 取餘數，餘數的可能為 [0, K - 1] ，共 K 個，所以取餘數超過 K 次結果必重覆，形成循環。當有循環時，還沒找到倍數即無解，所以檢查 K 項就能知道有沒有解。

```python
K = int(input())

def solve():
    val = 0
    for i in range(1, K + 1):
        val = val * 10 + 7
        val = val % K
        if val == 0:
            return i
    
    return -1

print(solve())
```

## D. Alter Altar

畫出幾個 testcase 研究研究，發現二個結論：

1. 最終字串必形成 `RRR...WWWW` 的型式。
2. 沒必要「改顏色」，只使用「交換」所需的操作數就是最小的。

第二點是因為「交換」一次將兩個位置處理好，「改顏色」一次只能處理好一個位置。

於是我的方法很簡單：將最終字串建出來跟原字串比一下，看有幾個位置需要處理，該值除上 2 即為答案。

```python
N = int(input())
C = input()
T = 'R' * C.count('R') + 'W' * C.count('W')
ans = sum([1 for a, b in zip(C, T) if a != b]) // 2
print(ans)
```

## E. Logs

最大值最小化，二分搜！對應的 check 為：看看是不是只需 K 刀或更少就能將 logs 分割成最長長度為 l，換一個講法是：將每個 log，都盡量切成最長的長度 l，看最後的總刀數是不是 <= K。這可以在 $O(N)$ 時間算出，因為在給定某個 log 的長度為 $a$ 時，將之分解成每塊最長長度都是 $l$ 所需的刀數為 $\lceil \frac{a}{l} \rceil - 1$，可以 $O(1)$ 得到。$\lceil \frac{a}{l} \rceil$ 代表的是最後有幾塊，減上 1 就是所需刀數。

```python
from math import ceil

N, K = map(int, input().split())
A = list(map(int, input().split()))

def check(l):
    cnt = sum(ceil(a / l) - 1 for a in A)
    return cnt <= K

lb, ub = 0, max(A)
for _ in range(50):
    mid = (lb + ub) / 2
    if check(mid):
        ub = mid
    else:
        lb = mid
print(ceil(ub))
```

## F. Range Set Query

這題好像是經典題，不過我沒解過。這題因為 $c_i$ 很大，我們無法將之塞進位元，然後用一個線段樹解決。

給定一個序列 `A`，我們建構一個序列 `B`，長度與 `A` 相同，且滿足一個特性：對於 `A` 中的每種數字 `d` 與 `d` 所在的那些位置，只有最右邊那個位置 `p`, `B[p] = 1`，`B` 的其他位置都是 `0`，以下給出一些例子：

序列 `A = [1, 2, 3]`，對應 `B = [1, 1, 1]`。
序列 `A = [1, 2, 1]`，對應 `B = [0, 1, 1]`。
序列 `A = [1, 1, 1]`，對應 `B = [0, 0, 1]`。

這樣的序列 B 讓我們有一個很好的特性，當我們想知道 A 有多少個不同值時，只要加總 B 就能得到結果，事實上這個結論對所有 A 的後綴都成立：**想知道 `A[i:]` 有多少不同值，答案為 `sum(B[i:])`**。

利用這個特性，對於題目所給的 C，我們可以設計出這樣的演算法：

```python
for i in range(len(C)):
    求出 C[:i] 所對應的 B
    若存在 query 的右界是 i, 即 (l, i)，那這個 query 的答案是 sum(B[l:])
```

顯然 `C[:i - 1]` 對應的 `B` 與 `C[:i]` 對應的 `B` 是非常像的。我們只需另一個陣列 `last` 記錄每種數字最後一次出現的地方，這樣當我們從 `i - 1` 迭代到 `i`，只需將 `B` 中位於 `last[C[i]]` 的那個 `1` 移至位置 `i`，就能高速地得到新的 `B`。另外為了高速的查詢 `B` 的後綴和，我們使用 SegTree 或 BIT 來存 `B`。

```python
seg = SegTree(len(N), 0) # B
last = defaultdict(lambda: -1) # -1 = not appeared yet

for i, c in enumerate(C):
    # Compute B corresponding to C[:i] and update last
    if last[c] != -1:
        seg.increment(last[c], -1)
    seg.increment(i, +1)
    last[c] = i
    
    # Compute ans if there are queries ending at i
    for query in queries_end_at_i:
        query.ans = seg.sum([query.l, query.r])
```

最後給出使用 BIT 的 C++ AC Code:

```cpp
#include <iostream>
#include <vector>
#include <tuple>
#include <algorithm>
using namespace std;

typedef tuple<int, int, int> t3i;

// API use 0-based index while internal implementation use 1-based index
template <class T>
struct BIT {
    int NN;
    vector<T> data;

    BIT(int N, T default_value) {
        NN = N;
        data = vector<T>(NN + 1, default_value);
    }

    void add_at(int idx, T val) {
        for (int i = idx + 1; i <= NN; i += (i & (-i))) {
            data[i] += val;
        }
    }

    T sum_to(int idx) const { // [0, idx]
        T res = 0;
        for (int i = idx + 1; i >= 1; i -= (i & (-i))) {
            res += data[i];
        }
        return res;
    }

    T sum_in(int l, int r) const { // [l, r]
        return sum_to(r) - sum_to(l - 1);
    }
};


template <class T>
ostream & operator << (ostream &out, const BIT<T> &bit) {
    out << "BIT(NN=" << bit.NN << ", ";
    out << "[ ";
    for (int i = 0; i < bit.NN; i++) {
        out << bit.sum_in(i, i) << " ";
    }
    out << "])";
    return out;
}


int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N, Q;
    cin >> N >> Q;
    
    auto C = vector<int>(N, 0);
    for (int i = 0; i < N; i++) {
        cin >> C[i];
    }

    auto queries = vector<vector<t3i>>(N, vector<t3i>());
    for (int i = 0; i < Q; i++) {
        int l, r; 
        cin >> l >> r;
        l--; r--;
        queries[r].push_back({i, l, r});
    }

    auto ans = vector<int>(Q, -1);
    auto V = *max_element(C.begin(), C.end());
    auto last = vector<int>(V + 1, -1);
    
    auto bit = BIT<int>(N, 0);
    for (int i = 0; i < N; i++) {
        if (last[C[i]] != -1) {
            bit.add_at(last[C[i]], -1);
        }
        bit.add_at(i, +1);
        last[C[i]] = i;

        for (auto&& [id, l, r]: queries[i]) {
            ans[id] = bit.sum_in(l, r);
        }
    }

    for (auto x : ans) {
        cout << x << endl;
    }

    return 0;
}
```

:::success
[< 回到所有題解](https://hackmd.io/@amoshyc/HkWWepaPv/)
:::