--- tags: Programming Contest --- # AtCoder Regular Contest 107 題解 [題目連結](https://atcoder.jp/contests/arc107) ## 心得 第二題鬼打牆,最終解出前三題,排 1245,積分 +35 ## A. Simple Math ```python A, B, C = map(int, input().split()) A = (1 + A) * A // 2 % 998244353 B = (1 + B) * B // 2 % 998244353 C = (1 + C) * C // 2 % 998244353 print(A * B * C % 998244353) ``` ## B. Quadruple 上一次在 cf 有類似題我輕鬆解出,這次卻鬼打牆。比賽時就想到要枚舉 $a + b$ 然後算出 $c + d = K + (a + b)$,此時的方法數為 $f(a + b) * f(c + d)$,$f(x)$ 代表的是將 $x$ 分解成二個正整數的方法數。 問題就在於這個 $f$ 表要怎麼有效率的算出來,比賽時想了半天只想到用線段樹來事先建表,雖然也能 [AC](https://atcoder.jp/contests/arc107/submissions/17756889),但明顯不是 intended solution。 賽後重新思考,發現是解聯立不等式,並可以 generalize: **將整數 $x$ 分解成 2 個整數 $x = a + b$,且 $a_{min} \le a \le a_{max}, b_{min} \le b \le b_{max}$,問相異的 $(a, b)$ 有多少個?** $$ min(x - a_{min}, b_{max}) - max(x - a_{max}, b_{min}) + 1 $$ 解聯立不等式,合併前兩個式子: $$ \begin{cases} a + b = x \\ a_{min} \le a \le a_{max} \\ b_{min} \le b \le b_{max} \end{cases} \implies \begin{cases} x - a_{max} \le b \le x - a_{min} \\ b_{min} \le b \le b_{max} \end{cases} $$ 可得 $b$ 的上下界: $$ max(x - a_{max}, b_{min}) \le b \le min(x - a_{min}, b_{max}) $$ 在此範圍內的 $b$ 都會有合法對應的 $a$。而 $b$ 的可能數量為「上界 - 下界 + 1」。 前述的 f 就是 $a_{min} = 1, a_{max} = N, b_{min} = 1, b_{max} = N$ 的特例,所以代公式即可。 ```python def f(x, a_min, a_max, b_min, b_max): if x < a_min + b_min or x > a_max + b_max: return 0 lb_b = max(x - a_max, b_min) ub_b = min(x - a_min, b_max) return ub_b - lb_b + 1 N, K = map(int, input().split()) ans = 0 for beta in range(2, 2 * N + 1): alpha = beta + K if 2 <= alpha <= 2 * N: ans += f(alpha, 1, N, 1, N) * f(beta, 1, N, 1, N) print(ans) ``` ## C. Shuffle Permutation 觀察可以發現,可以將 rows 分解成一群一群的,例如第一筆範測 rows 就是分解成二群: [[0, 2], [1]],群內的 rows 可以互相交換,所以 row 的方面可以有 2! * 1! 種變化。而 cols 也是一樣,第一筆範測的 cols 只有一群 [[0, 1, 2]],3! 種變化。而 row 與 col 彼此獨立,所以得到 (2! * 1!) * (3!) = 12 的答案。第二筆範測的答案是 10! * 10!。 實作上就 O(N^3) 枚舉 + dsu 將各個群找出來。 ```python from collections import defaultdict class DisjoinSet: def __init__(self, N): self.par = [-1] * N self.sz = [1] * N def root(self, x): if self.par[x] < 0: return x else: self.par[x] = self.root(self.par[x]) return self.par[x] def unite(self, a, b): a = self.root(a) b = self.root(b) if a == b: return if self.sz[a] > self.sz[b]: a, b = b, a self.par[a] = b self.sz[b] += self.sz[a] def same(self, a, b): return self.root(a) == self.root(b) def size(self, x): return self.sz[self.root(x)] def groups(self): clusters = defaultdict(list) for x in range(N): clusters[self.root(x)].append(x) return list(clusters.values()) N, K = map(int, input().split()) M = 998244353 A = [[int(x) for x in input().split()] for _ in range(N)] fact = [1] * (N + 1) for i in range(1, N + 1): fact[i] = fact[i - 1] * i % M row_dsu = DisjoinSet(N) for r1 in range(N): for r2 in range(r1, N): if all(A[r1][c] + A[r2][c] <= K for c in range(N)): row_dsu.unite(r1, r2) col_dsu = DisjoinSet(N) for c1 in range(N): for c2 in range(c1, N): if all(A[r][c1] + A[r][c2] <= K for r in range(N)): col_dsu.unite(c1, c2) cntR = 1 for group in row_dsu.groups(): cntR = cntR * fact[len(group)] % M cntC = 1 for group in col_dsu.groups(): cntC = cntC * fact[len(group)] % M print(cntR * cntC % M) ``` ## D. Number of Multisets 這題與 [POJ 2229](https://gist.github.com/amoshyc/9b46373dfaeb9904ab65) 類似,但加了個數量為 N 的條件,因此 dp 也增一個維度,再根據這題調整。 P.S. 我 5 年前的題解,真的古老,原來五年來我根本沒什麼進步 (\~_\~;;;) 首先我們先把一些簡單數字的答案[爆出來](https://pastebin.com/XTZrLayB),以下是 N = 6 時,K = 1 ~ 6 的組合方法: ``` 6 : 1/1, 1/1, 1/1, 1/1, 1/1, 1/1 ---------- 5 : 1/1, 1/1, 1/1, 1/1, 1/2, 1/2 ---------- 4 : 1/1, 1/1, 1/1, 1/2, 1/4, 1/4 1/1, 1/1, 1/2, 1/2, 1/2, 1/2 ---------- 3 : 1/2, 1/2, 1/2, 1/2, 1/2, 1/2 1/1, 1/1, 1/2, 1/4, 1/8, 1/8 1/1, 1/1, 1/4, 1/4, 1/4, 1/4 1/1, 1/2, 1/2, 1/2, 1/4, 1/4 ---------- 2 : 1/1, 1/2, 1/4, 1/8, 1/16, 1/16 1/1, 1/2, 1/8, 1/8, 1/8, 1/8 1/1, 1/4, 1/4, 1/4, 1/8, 1/8 1/2, 1/2, 1/2, 1/4, 1/8, 1/8 1/2, 1/2, 1/4, 1/4, 1/4, 1/4 ---------- 1 : 1/4, 1/4, 1/4, 1/8, 1/16, 1/16 1/2, 1/4, 1/16, 1/16, 1/16, 1/16 1/2, 1/4, 1/8, 1/16, 1/32, 1/32 1/2, 1/8, 1/8, 1/8, 1/16, 1/16 1/4, 1/4, 1/8, 1/8, 1/8, 1/8 ---------- ``` 以 K = 2 為例,他的 5 種分解方法可以拆成 2 部份: 含 1/1 的: ``` 1/1, 1/2, 1/4, 1/8, 1/16, 1/16 1/1, 1/2, 1/8, 1/8, 1/8, 1/8 1/1, 1/4, 1/4, 1/4, 1/8, 1/8 ``` 與不含 1/1 的: ``` 1/2, 1/2, 1/2, 1/4, 1/8, 1/8 1/2, 1/2, 1/4, 1/4, 1/4, 1/4 ``` 不含 1/1 的部份,把每一項都乘 2,就是 K = 4 時的組合。 其他數字也可以此類推,K = k 不含 1/1 的部份每項乘 2 就是 K = 2k 的組合。 這引導我們設計出:`dp[i, j]` = 將數字 j 分解成 i 個值的方法數。 考慮有含與沒含 1/1 的部份,得到轉移方程:`dp[i, j] = dp[i - 1, j - 1] + dp[i, 2 * j]`。 實作上小心計算順序: ```python N, K = map(int, input().split()) M = 998244353 dp = [[0 for _ in range(N + 1)] for _ in range(N + 1)] dp[0][0] = 1 for i in range(1, N + 1): for j in range(N, 0, -1): dp[i][j] = dp[i - 1][j - 1] if 2 * j <= N: dp[i][j] += dp[i][2 * j] dp[i][j] %= M print(dp[N][K]) ``` :::success [< 回到所有題解](https://hackmd.io/@amoshyc/HkWWepaPv/) :::