競プロ参戦記 #74 Divisible Substring | ABC 158

AtCoder Beginner Contest 158 のA-E問題の考察を書いていきます。

本番は寝過ごしたので終了直後にやってみたところ、ぎりぎりE完 (3WA) という感じでした。

- 前回: [競プロ参戦記 #73 Yakiniku Optimization Problem | ABC 157](https://qiita.com/vain0x/items/ef30634e85f5112defce)
- 問題一覧: https://atcoder.jp/contests/abc158/tasks

## A - Station and Bus

問題概要: 市内には3つの駅がある。i 番目の駅は鉄道会社 S(i) が管理している。鉄道会社 A が管理している駅と、鉄道会社 B が管理している駅の間には、バスが運行することになった。これらの駅の間にバスが運行しているかどうかを判定せよ。

制約:

- |S| = 3
- S(i) は A または B

### A: 考察

S に A と B の両方が含まれていたら、それらの駅の間にバスが運行するので Yes、そうでなければ No となります。

「A と B の両方が含まれる」ような S は6通り、逆に「A と B の片方しか含まれない」のは2通りです。後者を検査した方が短く書けます。

```rust
    S != "AAA" && S != "BBB"
```

筆者の回答 (Rust, AC): https://atcoder.jp/contests/abc158/submissions/10640927

## B - Count Balls

問題概要: 列の末尾に A 個の青いボールを追加し、列の末尾に B 個の赤いボールを追加する、という操作を 10^100 回行う。列の前から N 個のボールのうち、青いボールの個数を求めよ。

制約: A+B, N≤10^18

### B: 考察

整数の端数切り捨ての除算を x//y と書きます。

例えば A=3, B=2 なら、ボールの列は以下のようになります。10^100 が目を引きますが、単に列の長さが N 以上であるといっているだけです。

    青青青赤赤 青青青赤赤 青青青赤赤 青青青赤赤...

「青青青赤赤」のような A+B 個のボールからなるブロックは、先頭 N 個のボールに N//(A+B) 個含まれています。そのうち青いボールは、1ブロックあたりA個なので、(N//(A+B))・A 個です。

N が (A+B) の倍数でなければ、最後の1ブロックの途中までが「先頭 N 個」に含まれます。最後のブロックには N % (A+B) 個のボールが含まれています。そのうち青いボールは、前から A 個だけなので min(N % (A+B), A) 個です。

筆者の回答 (Rust, AC): https://atcoder.jp/contests/abc158/submissions/10640826

## C - Tax Increase

問題概要: ある商品は、消費税率が 8% のとき A 円の消費税が課され、消費税率が 10% のとき B 円の消費税が課される。商品の税抜き価格がこの条件を満たすことがありうるか判定し、ありうるなら最小の税抜き価格を求めよ。(消費税の端数は切り捨てられる。)

制約: A, B≤100

### C: 考察

x 以下の最大の整数を floor(x)、x 以上の最小の整数を ceil(x) と表します。

条件を満たす税抜き価格を X とおくと、次の連立方程式が立ちます。

- floor(X・8/100) = A
- floor(X・10/100) = B

floor を使わない形に変形します。floor(x) は x を超えないので floor(x) ≤ x であり、最大なので x-1 < floor(x) です。

- X・8/100 - 1 < A ≤ X・8/100
- X・10/100 - 1 < B ≤ X・10/100

X について変形すると:

- X < (A+1)・100/8
- A・100/8 ≤ X
- X < (B+1)・100/10
- B・100/10 ≤ X

まとめると:

- max(A・100/8, B・100/10) ≤ X < min((A+1)・100/8, (B+1)・100/10)

したがって、X の最小値 u と最大値 v が求まります。

- u = max(ceil(A・100/8), B・100/10)
- v = min(ceil((A+1)・100/8), (B+1)・100/10) - 1

u > v なら X は存在せず、u≤v なら X=u が最小です。

筆者の回答 (Rust, AC): https://atcoder.jp/contests/abc158/submissions/10644093

### C: 別解

制約から X < 10^4 なので、全探索で求まります。このごろ全探索が見えてなくて反省……

## D - String Formation

問題概要: 英小文字からなる文字列 S が与えられる。次の Q 個の操作を順番に行った後の文字列を求めよ。

- i 番目の操作は以下の2種類のどちらか:
    - T(i)=1 のとき: 文字列 S を反転する。
    - T(i)=2 のとき: 整数 F(i) と英小文字 C(i) が与えられる。
        - F(i)=1 のとき: 文字列 S の先頭に C(i) を追加する。
        - F(i)=2 のとき: 文字列 S の末尾に C(i) を追加する。

制約: |S|, Q≤10^5

### D: 考察

実際にこれらの操作を行うと TLE します。

反転操作を実際には行わず、文字列が反転状態にあるかどうかをフラグで持てば、反転操作を最後まで遅延できます。また、先頭への追加と末尾への追加は、列を両端キュー ([VecDeque](https://doc.rust-lang.org/std/collections/struct.VecDeque.html)) で持つことにより高速化可能です。

全体として O(|S| + Q) 時間で間に合います。

筆者の回答 (Rust, AC): https://atcoder.jp/contests/abc158/submissions/10641247

----

「操作列の変形」という視点で考えると面白いと思います。

先頭に文字 c を追加する操作を「先頭(c)」、末尾への追加操作を「末尾(c)」と書くことにします。

例えば S=cde, 操作=(先頭(b), 末尾(f), 反転, 先頭(g), 末尾(a), 反転) という入力をやってみると、以下の通りです。

| 操作    | 操作後  |
|:-------|:------|
|        | cde   |
| 先頭(b) | bcde  |
| 末尾(f) | bcdef |
| 反転    | fedcb |
| 先頭(g) | gfedcb |
| 末尾(a) | gfedcba |
| 反転    | abcdefg |

「反転→先頭(c)」という操作列は「末尾(c)→反転」に、「反転→末尾(c)」という操作列は「先頭(c)→反転」という操作列に、それぞれ変形しても最終結果が変わりません。この変形を可能なかぎり適用すると、反転操作を操作列の最後に移動できます。

| 操作        | 操作後   |
|:-----------|:--------|
|            | cde     |
| 先頭(b)     | bcde    |
| 末尾(f)     | bcdef   |
| **末尾**(g) | bcdefg  |
| **先頭**(a) | abcdefg |
| **反転**    | gfedcba |
| 反転        | abcdefg |

2回連続の反転は無視していいので、反転は最大1回になります。

また、先頭への追加操作と末尾への追加操作も交換できます。つまり「先頭(a)→末尾(b)」と「末尾(b)→先頭(a)」は、最終的には同じ結果になります。そのため、先頭(c)を前方に、末尾(c)を後方にまとめても OK です。

| 操作    | 操作後   |
|:-------|:--------|
|        | cde     |
| 先頭(b) | bcde    |
| 先頭(a) | abcde   |
| 末尾(f) | abcdef  |
| 末尾(g) | abcdefg |

したがって、操作列は「先頭に文字列 X を追加する」「末尾に文字列 Y を追加する」「n 回反転する」というかたちに変形できます。クエリを見て X, Y, n を求め、(X + S + Y) を計算して、n 回反転したら答えが得られます。

実際に書くコードは上記の回答とだいたい同じです。

## E - Divisible Substring

問題概要: 数字からなる長さ N の文字列 S と、素数 P が与えられる。S の空でない区間で、それを10進表記の整数をみなした際に素数 P で割り切れるものの個数を数えよ。(先頭が 0 で始まる10進表記の整数も認める。)

制約: N≤10^5

### E: 考察

例えば S=31415, P=3 だと、次の4つの区間があります。

```
    31415
    ^       3
    ^^^^    3141
     ^^^    141
       ^^   15
```

S の l 文字目から r-1 文字目までの文字列を10進表記の整数とみなしたものを f(l, r) とおきます。f を使って問題を形式的に書きなおすと:

> f(l, r) ≡ 0 (mod P) となるペア (l, r) の個数を求めよ (0 ≤ l < r ≤ N)

となります。

f(l, r) をすべて計算するのは O(N^2) 時間かかって間に合いませんが、r=N に固定した f(l, N) だけなら、累積和により O(N) 時間で計算できます。一般の f(l, r) は f(l, N) の式で表せそうなので、そのあたりから考察が進みそうです。

f(l, r) を f(r, N) の桁数だけずらしたものに f(r, N) を足すと f(l, N) に等しくなる (例えば 31415 = 31 * 1000 + 415 のようになる) ことから、次が成り立ちます。

$$
f(l, N) = 10^{N-r} f(l, r) + f(r, N)
$$

**もし P が 10 と互いに素なら** 10^(N-r) で両辺を割れるので、f(l, r) について変形して:

$$
f(l, r) = 10^{r-N} (f(l, N) - f(r, N))
$$

を得ます。こうして次が分かりました。

```math
\begin{align}
    f(l, r) & ≡ 0 \\
    ⇔ f(l, N) & ≡ f(r, N) \quad (\text{mod}\ P)
\end{align}
```

言い換えると、f(i, N) % P の値が等しいような i を2つもってきてそれぞれ l, r とすれば f(l, r) ≡ 0 (mod P) が成り立つということです。

したがって、f(i, N) % P の値で i を分類し、各剰余に対応する i の個数が n 個であれば n(n-1)/2 個のペアを計上すれば OK です。全体として O(N) 時間で解けました。

----

忘れてはいけないのが P が10と互いに素でないケース、つまり P = 2 と P = 5 です。このとき 10 ≡ 0 (mod P) だから 10 は逆元を持たないので、上記の「両辺を 10^(N-r) で割る」操作は NG です。

P = 2 のケースは簡単で、偶数の10進表記は一番下の桁が偶数なので、

    f(l, r) ≡ 0 (mod 2)
    ⇔ S(r-1) が偶数

といえます。したがって、r に関してループを回すことで O(N) 時間で解けます。

P=5 も同様に、5の倍数の10進表記は一番下の桁が 0 か 5 であることを利用して解けます。

筆者の回答 (Rust, AC): https://atcoder.jp/contests/abc158/submissions/10645641