--- tags: Programming Contest --- # AtCoder Beginner Contest 183 題解 ## 心得 解出 5 題,積分 +54。 第四題漏看了一個條件,害我花了好久。 第六題竟然直接裸著來,我以為這方法會 MLE 就沒寫,可惜。 ![](https://i.imgur.com/RPxBc1q.gif) [source](https://home.gamer.com.tw/creationDetail.php?sn=3060065) ## A. [ReLU](https://atcoder.jp/contests/abc183/tasks/abc183_a) 38 秒,下次挑戰不先測直接 submit XD。 ## B. [Billiards](https://atcoder.jp/contests/abc183/tasks/abc183_b) 入射角 = 反射角這樣的反彈,就會想到對稱啦。將 G 對著 x-axis 對稱,然後求出 SG 的直線方程,再把 y = 0 代入即為所求。當使用點斜式時,記得小心斜率可能無窮大,不過這題保證 S.x ≠ G.x,因此不會發生。 ```python sx, sy, gx, gy = map(int, input().split()) dx = gx - sx dy = -gy - sy ans = sx + dx * (-sy) / dy print('{:.15f}'.format(ans)) ``` ## C. [Travel](https://atcoder.jp/contests/abc183/tasks/abc183_c) 直接 8! 全排列爆開。 ```python from itertools import permutations N, K = map(int, input().split()) T = [list(map(int, input().split())) for _ in range(N)] ans = 0 for perm in permutations(range(N)): if perm[0] != 0: continue need = sum(T[perm[i - 1]][perm[i]] for i in range(1, N)) + T[perm[-1]][0] if need == K: ans += 1 print(ans) ``` ## D. [Water Heater](https://atcoder.jp/contests/abc183/tasks/abc183_d) 因為之前注的水不能給之後的人用,所以只需考慮有沒有任何一個時刻,所需的總水量大於能供給的水量。實作方法為將 $(s, t, p)$ 視為在時刻 $s$ 增加 $p$,在時刻 $t$ 減少 $p$,這樣當我們從時間 0 迭代到時間 $t$ 時,**累計**的總數就是時刻 $t$ 的需求。 ```python N, W = map(int, input().split()) events = [] for _ in range(N): s, t, p = map(int, input().split()) events.append((s, +p)) events.append((t, -p)) events.sort() cum_sum = 0 for (t, p) in events: cum_sum += p if cum_sum > W: print('No') break else: print('Yes') ``` ## E. [Queen on Grid](https://atcoder.jp/contests/abc183/tasks/abc183_e) 設 $dp[r, c]$ 為從起點走至 $(r, c)$ 的方法數。 考慮 queen 的三種移動方式,當前位置可能是「從上方來的、從左邊來的、從左上來的」,我們可以寫出轉移方程: $$ dp[r, c] = \sum_{0 \le i \lt r} dp[i, c] + \sum_{0 \le i \lt c} dp[r, i] + \sum_{0 \le i \lt min(r, c)} dp[r - i, c - i] $$ 明顯這個 dp 會 TLE,讓我們想辦法優化。一個常見手法是使用額外容器記錄之前 dp 值: 1. 一個長度為 `W` 的 list `cntC` 記錄每個 column 目前的累計 $dp$。 2. 一個長度為 `H` 的 list `cntR` 記錄每個 row 目前的累計 $dp$。 3. 一個長度為 `H + W - 1` 的 list `cntD` 記錄每個 diagonal 目前的累計 $dp$。 如果 $(r, c)$ 是障礙物,記得重設(清零)各個累計 $dp$。 當我們算出 $dp[r, c]$ 後,記得更新這些容器。 實作上,如何處理對角線會是一個難題,但我想到我以前有將之寫成[模版](https://amoshyc.github.io/CPsolution/template/math/index_of_diagonal.html#d)。 $(r, c)$ 的如果使用 $H - 1 - r + c$ 作為對角線編號會形成如下: $$ \begin{split}\begin{pmatrix} 3 & 4 & 5 & 6 & 7 \\ 2 & 3 & 4 & 5 & 6 \\ 1 & 2 & 3 & 4 & 5 \\ 0 & 1 & 2 & 3 & 4 \\ \end{pmatrix}\end{split} $$ ```python H, W = map(int, input().split()) mod = 10 ** 9 + 7 S = [input() for _ in range(H)] dp = [[0 for _ in range(W)] for _ in range(H)] cntR = [0 for _ in range(H)] # row cntC = [0 for _ in range(W)] # col cntD = [0 for _ in range(H + W - 1)] # diagonal for r in range(H): for c in range(W): if S[r][c] == '#': cntR[r] = 0 cntC[c] = 0 cntD[H - 1 - r + c] = 0 continue if r == 0 and c == 0: dp[r][c] = 1 else: if r > 0: dp[r][c] += cntC[c] if c > 0: dp[r][c] += cntR[r] if r > 0 and c > 0: dp[r][c] += cntD[H - 1 - r + c] dp[r][c] %= mod cntR[r] = (cntR[r] + dp[r][c]) % mod cntC[c] = (cntC[c] + dp[r][c]) % mod cntD[H - 1 - r + c] = (cntD[H - 1 - r + c] + dp[r][c]) % mod print(dp[H - 1][W - 1]) ``` ## F. [Confluence](https://atcoder.jp/contests/abc183/tasks/abc183_f) 又是集群又是合併,Disjoint Set 無誤啦。 難點是如何處理 class,一個直覺的想法是對 dsu 中每個 tree 都記錄該 tree 有幾個 class 1, 幾個 class 2, etc,然後這個方法就能 AC 了。我比賽中一直以為這個方法要 $O(N C)$ 的空間,因此會 MLE。但實際上如果用 `dict` 來存各 class 數量,所需空間與時間就能少上許多。 實作上可以修改 dsu 的程式碼,把 `dict` 放進去。因為我不能改模板太多,所以 `dict` 我是放在 dsu 之外,dsu 只需額外告訴我,合併 tree a 與 tree b 時,是 a 併入 b 還是 b 併入 a,我對應地修改 `dict`。這題我有嘗試用 `collections.Counter` 來代替 `dict`,但會 TLE,果然寫 python 真的不能用 `Counter`。 ```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 None, None if self.sz[a] > self.sz[b]: a, b = b, a self.par[a] = b self.sz[b] += self.sz[a] return a, b def same(self, a, b): return self.root(a) == self.root(b) def size(self, x): return self.sz[self.root(x)] def __str__(self): clusters = defaultdict(list) for x in range(N): clusters[self.root(x)].append(x) return str(list(clusters.values())) N, Q = map(int, input().split()) C = [int(x) for x in input().split()] dsu = DisjoinSet(N) rec = [defaultdict(int, {C[i]: 1}) for i in range(N)] for _ in range(Q): cmd, a, b = map(int, input().split()) if cmd == 1: a, b = a - 1, b - 1 src, dst = dsu.unite(a, b) if src is not None: for k, v in rec[src].items(): rec[dst][k] += v rec[src].clear() else: a = a - 1 root = dsu.root(a) print(rec[root][b]) ``` :::success [< 回到所有題解](https://hackmd.io/@amoshyc/HkWWepaPv/) :::