---
tags: Programming Contest
---
# AtCoder Beginner Contest 182 題解
## 心得
第四題又卡住,還好最後一分鐘 AC,應該先去解第五題,簡單多了 (︶︹︺)
把上一場加的積分全還回去了 QQ
## A, B
簽到。
## C. [To 3](https://atcoder.jp/contests/abc182/tasks/abc182_c)
基於 3 倍數的特性,可以得出不可能刪 3 個 digits 或更多。刪 1 個 digit 的情況為有某個 digit 與 「N 對 3 的餘數(r)」相同。刪 2 個的情況是選兩個 digits 出來,他們的和可以組出 r。實作時小心別讓所有的 digits 都被刪掉了。
```python
from itertools import combinations
N = input()
r = int(N) % 3
A = [int(c) % 3 for c in N]
if r == 0:
print(0)
elif len(A) >= 2 and any(a == r for a in A):
print(1)
elif len(A) >= 3 and any((a + b) % 3 == r for a, b in combinations(A, 2)):
print(2)
else:
print(-1)
```
## D. [Wandering](https://atcoder.jp/contests/abc182/tasks/abc182_d)
假設我們看第 $i$ 次操作,我們的位置為這些(假設之前的位置為 `prev[i]`):
```
prev[i]
prev[i] + A[0]
prev[i] + A[0] + A[1]
prev[i] + A[0] + A[1] + A[2]
...
prev[i] + A[0] + A[1] + A[2] + ... + A[i - 1] + A[i]
```
我們想找出其中最大的值:
$$
prev_i + max(0, \sum_{j=0}^0 A_j, \sum_{j=0}^1 A_j, \cdots, \sum_{j=0}^i A_j)
$$
設 $P$ 為 `A` 的前綴,我們可以簡化式子為:
$$
prev_i + max(0, P_0, P_1, \cdots, P_i)
$$
有了這個想法後,讓我們將第 $i-1$ 次操作與第 $i$ 次操作所求的值都寫出來:
$$
\begin{align}
&prev_{i-1} + max(0, P_0, P_1, \cdots, P_{i - 1})
\\
&prev_i + max(0, P_0, P_1, \cdots, P_{i - 1}, P_i)
\end{align}
$$
可以發現我們能輕易的從第 $i-1$ 次操作轉換至第 $i$ 次操作。
根據題意,式子的第一項是這樣轉換:
$$
prev_i = prev_{i - 1} + P_i
$$
而式子的第二項則為
$$
max(0, P_0, P_1, \cdots, P_{i - 1}, P_i) = max(max(0, P_0, P_1, \cdots, P_{i - 1}), P_i)
$$
這暗示了我們可以一個一個迭代操作,然後找出該操作所求的最大值。
完整的程式為:
```python
N = int(input())
A = [int(x) for x in input().split()]
prev = 0
pref = 0
mx_pref = 0
ans = 0
for i in range(N):
pref += A[i]
mx_pref = max(mx_pref, pref)
ans = max(ans, prev + mx_pref)
prev += pref
print(ans)
```
## E. [Akari](https://atcoder.jp/contests/abc182/tasks/abc182_e)
這題類似於之前比賽 [某題](https://hackmd.io/@amoshyc/Skr-nHJDP#E-Lamps) 的簡單版。
解法不只一種,而我的是開 4 個 H * W 的陣列,分別記錄會不會被「上、下、左、右」的燈炮點亮:
```
visL[r][c] = True if (r, c) will be lit by a bulb in (r, k) (0 <= k < c)
visR[r][c] = True if (r, c) will be lit by a bulb in (r, k) (c < k < W)
visT[r][c] = True if (r, c) will be lit by a bulb in (k, c) (0 <= k < c)
visD[r][c] = True if (r, c) will be lit by a bulb in (k, c) (c < k < H)
```
這 4 個陣列都可以用簡單 dp 求出。考慮該方向相鄰的格子是不是 block,我們可以得出:
```
visL[r][c] = False if grid[r][c - 1] is BLOCK else visL[r][c - 1]
```
同理類推。最後統計一下有多少個格子,被一個或多個方向的燈炮點亮。
```python
import sys
input = sys.stdin.readline
H, W, N, M = map(int, input().split())
EMPTY = 0
BULB = 1
BLOCK = 2
grid = [[EMPTY for _ in range(W)] for _ in range(H)]
for _ in range(N):
r, c = map(int, input().split())
grid[r - 1][c - 1] = BULB
for _ in range(M):
r, c = map(int, input().split())
grid[r - 1][c - 1] = BLOCK
visL = [[False for _ in range(W)] for _ in range(H)]
visR = [[False for _ in range(W)] for _ in range(H)]
visT = [[False for _ in range(W)] for _ in range(H)]
visD = [[False for _ in range(W)] for _ in range(H)]
for r in range(H):
for c in range(W):
if grid[r][c] == BULB:
visL[r][c] = True
visR[r][c] = True
visT[r][c] = True
visD[r][c] = True
for r in range(H):
for c in range(1, W):
if grid[r][c] == EMPTY:
visL[r][c] = False if grid[r][c - 1] == BLOCK else visL[r][c - 1]
for c in range(W - 2, -1, -1):
if grid[r][c] == EMPTY:
visR[r][c] = False if grid[r][c + 1] == BLOCK else visR[r][c + 1]
for c in range(W):
for r in range(1, H):
if grid[r][c] == EMPTY:
visT[r][c] = False if grid[r - 1][c] == BLOCK else visT[r - 1][c]
for r in range(H - 2, -1, -1):
if grid[r][c] == EMPTY:
visD[r][c] = False if grid[r + 1][c] == BLOCK else visD[r + 1][c]
ans = 0
for r in range(H):
for c in range(W):
if any([visL[r][c], visR[r][c], visT[r][c], visD[r][c]]):
ans += 1
print(ans)
```
## F. [Valid payments](https://atcoder.jp/contests/abc182/tasks/abc182_f)
這題有兩種解法。
### 解法 1
題目相當於在問:「所有 $A$ 進制的 $N$ 位數中(允許 leading 0)有幾個數 $r$ 滿足 $r$ 與 $X + r$ 沒有共同不為 $0$ 的 digit」,即 $\forall_{0 \le i \lt N}, r_i = 0 \lor (X + r)_i = 0$。
而這個可以透過 digit dp 求解,因為我們在處理加法,會有進位的問題,所以讓我們從低位數往高位數填數字,而非從高位往低位。
下圖為直式加法示意圖,$c$ 是 carry,$x$ 是 $X$ 的 $A$ 進制,$y$ 是 $X+r$ 的 $A$ 進制。注意我們是在 $A$ 進制底下,當第 $i$ 位達到 $m_i = \frac{A_{i + 1}}{A_{i}}$ 時就會進位。
$$
\begin{array}{r}
&c_3 &c_2 &c_1 &0 \\
&x_3 &x_2 &x_1 &x_0 \\
+\quad
&r_3 &r_2 &r_1 &r_0 \\
\hline
&y_3 &y_2 &y_1 &y_0
\end{array}
$$
其中每一位 $i$ 都滿足:
$$
\begin{align}
y_i &= (c_i + x_i + r_i) \pmod {m_i} \\
c_{i + 1} &= \lfloor \frac{c_i + x_i + r_i}{m_i} \rfloor
\end{align}
$$
設 $dp[i, c]$ 代表填好 $[0, i)$ 這些位的數字時的答案,且第 $i$ 位的 carry 是 $c$。
枚舉 $r_i$ 所有的值($0 \le r_i \lt m_i$),我們得到遞推轉移:
```python
for i in range(N):
for c in range(2):
for r in range(mx[i]):
val = c + x[i] + r
y = val % mx[i]
if r == 0 or y == 0:
new_c = val // mx[i]
dp[i + 1][new_c] += dp[i][c]
```
很可惜這個 dp 會 TLE,我們嘗試優化第三個迴圈,因為 $r_i = 0 \lor y_i = 0$ 的限制,不可能存在太多可行的情況。讓我們將 $r_i = 0 \lor y_i = 0$ 分解成 3 個 disjoint 的條件:
1. $r_i = 0 \land y_i = 0$
2. $r_i = 0 \land y_i \ne 0$
3. $r_i \ne 0 \land y_i = 0$
讓我們看看在什麼情況下各個條件會成立:
1. 將 $r_i = 0 \land y_i = 0$ 代入第 $i$ 位的公式,可以看出僅當 $(c_i + x_i) = 0 \pmod {m_i}$ 時,條件 1 能成立,此時我們轉移至 $dp[i + 1, c_{i+1}]$。
2. 將 $r_i = 0$ 代入第 $i$ 位的公式,可以發現僅當 $(c_i + x_i) \ne 0 \pmod {m_i}$ 時能讓 $y_i \ne 0$,此時我們轉移至 $dp[i + 1, c_{i+1}]$。
3. 將 $y_i = 0$ 代入第 $i$ 位的公式,可以發現僅當 $(c_i + x_i) \ne 0 \pmod {m_i}$ 時能讓 $r_i \ne 0$,且此時一定會有進位發生,我們轉移至 $dp[i + 1, 1]$。
因為條件之間是 disjoint 所以滿足加法原理,我們得到最終的程式:
```python
N, X = map(int, input().split())
A = [int(x) for x in input().split()]
mx = []
for i in range(N - 1):
mx.append(A[i + 1] // A[i])
mx.append(10 ** 18)
x = []
for a in reversed(A):
q, X = divmod(X, a)
x.append(q)
x = x[::-1]
dp = [[0, 0] for _ in range(N + 1)]
dp[0][0] = 1
for i in range(N):
for c in range(2):
# r[i] = 0 and y[i] = 0
if (c + x[i]) % mx[i] == 0:
new_c = (c + x[i]) // mx[i]
dp[i + 1][new_c] += dp[i][c]
# r[i] = 0 and y[i] ≠ 0
if (c + x[i]) % mx[i] != 0:
new_c = (c + x[i]) // mx[i]
dp[i + 1][new_c] += dp[i][c]
# r[i] ≠ 0 and y[i] = 0
if (c + x[i]) % mx[i] != 0:
dp[i + 1][1] += dp[i][c]
print(dp[N][0])
```
### 解法 2
來源:[maspy 的題解](https://maspypy.com/atcoder-%e5%8f%82%e5%8a%a0%e6%84%9f%e6%83%b3-2020-11-08abc-182#toc3)
我們可以將任何 10 進制的非負整數 $n$ 表達成 $A$ 進制:$n_{10} = \vec b_{[A]} = [b_{N-1}, b_{N-2}, \cdots, b_{0}]_{[A]}$,例如第二筆範測:$198_{10} = \vec b_{[A]} = [1, 1, 4, 1, 3]_{[A]}$。
定義函式 $f(\vec b_{[A]}, i)$ 為購買 $\vec b_{[A]} = [b_{N-1}, b_{N-2}, \cdots, b_{0}]_{[A]} = p_{10}$ 元商品的可行花費方法數,且 $b_{i - 1}, b_{i-2}, \cdots, b_{0}$ 都是 $0$,則題目所求為 $f(X_{10}, 0)$。
首先我們考慮基底情況 $f(\vec b_{[A]}, N - 1)$,即商品價格是 $\vec b_{[A]} = [b_{N-1}, 0, \cdots, 0]_{[A]} = (b_{N-1} A_{N-1})_{10}$,此時可行的花費方法數只有一種:付剛剛好的錢,得到找零 0 元,因此 $\forall \vec b_{[A]}, f(\vec b_{[A]}, N - 1) = 1$。
接著考慮一般情況 $f(\vec b_{[A]}, i)$,即商品價格是 $\vec b_{[A]} = [b_{N-1}, b_{N-2}, \cdots, b_{i}, 0, 0, \cdots, 0]_{[A]}$。根據題意,我們花費的金額 $y_{10} = \vec y_{[A]} = [y_{N-1}, y_{N-2}, \cdots, y_0]_{[A]}$ 與找零 $c_{10} = \vec c_{[A]} = [c_{N-1}, c_{N-2}, \cdots, c_0]_{[A]}$ 不能有共同都不為 0 的 digit。
只看第 $i$ 個的 digit,即是 $y_i = 0 \lor c_i = 0$。同時在第 $i$ 位上我們也得滿足「找零 = 花費 - 價格」的關係:$c_i = y_i - b_i \pmod {A_{i + 1}}$。我們可以根據 $b_i$ 分情況討論。
若 $b_i = 0$,則 $y_i = c_i = 0$,$y_i, c_i$ 只有這一種可能,情況轉變成 $f([b_{N-1}, \cdots, b_{i + 1}, \color{orange}{0}, \cdots, 0], i + 1)$。
若 $b_i \ne 0$,則情況有兩種:$y_i = 0 \lor c_i = 0$。若 $c_i = 0$,說明 $y_i = b_i$,花費與價格在這一位上是相同的,可以視為 $b_i = 0$ 的情況,情況轉變成 $f([b_{N-1}, \cdots, b_{i + 1}, \color{orange}{0}, \cdots, 0], i + 1)$。若 $y_i = 0$,說明花費在這一位上是 0,但找零是非 0,這代表我們有向更高一位的數**借位**,因此情況轉變至 $f([b_{N-1}, \cdots, \color{red}{b_{i + 1} + 1}, \color{orange}{0}, \cdots, 0], i + 1)$。
至此整個 dp 就出來了,實作上不需要真的把 $\vec b_{[A]}$ 當成函式的參數,我們使用他的 10 進制,再搭配上 top down 的 dp,程式可以簡單的寫成:
```python
from functools import lru_cache
@lru_cache(maxsize=None)
def f(p, i):
if i == N - 1:
return 1
b_i = p % A[i + 1]
if b_i == 0:
return f(p, i + 1)
else:
cnt1 = f(p - b_i, i + 1) # c_i = 0
cnt2 = f(p - b_i + A[i + 1], i + 1) # y_i = 0
return cnt1 + cnt2
N, X = map(int, input().split())
A = [int(x) for x in input().split()]
print(f(X, 0))
```
:::success
[< 回到所有題解](https://hackmd.io/@amoshyc/HkWWepaPv/)
:::