【AtCoder】PythonでABC317を解く
はじめに
ABC317のA,B,C,D問題をPython3で解きます。
A問題: Potions
問題の概要
個の効き目順に並んだ傷薬があり、番目の傷薬を使うと体力がだけ増加する。現体力がで傷薬を1つ使って体力を以上するとき、その条件を満たす最も効き目の弱い傷薬の番号を答えよ。
問題の考察
を満たす最も効き目の弱い傷薬番号を答えます。
コード
def solve(H, X, P): for i, p in enumerate(P, 1): if H + p >= X: return i return len(P) def main(): N, H, X = map(int, input().split()) P = list(map(int, input().split())) ans = solve(H, X, P) print(ans) if __name__ == "__main__": main()
B問題: MissingNo.
問題の概要
個の連続する整数のうちの一つをなくした。入力として与えられる残りの整数の列 から、なくした整数を求めよ。ただし、なくした整数は一意に決まる入力のみが与えられる。
問題の考察
なくした整数は一意に決まる入力のみが与えられるという制約から、両端の整数以外がなくなるのだと分かります。両端の整数は必ず残っているため、集合からを除いていくと、残った1つがなくした整数に対応します。
コード
def solve(N, A): minA = min(A) maxA = max(A) V = set(range(minA, maxA + 1)) for a in A: V.discard(a) return V.pop() def main(): N = int(input()) A = list(map(int, input().split())) ans = solve(N, A) print(ans) if __name__ == "__main__": main()
C問題: Remembering the Days
問題の概要
個の街と本の道路がある。本目の道路は長さで街と街を双方向に結ぶ。好きな街からスタートして同じ街を通らずに移動するとき、通る道路の長さの和の最大値を求めよ。
問題の考察
が最大でも10と小さいため、深さ優先探索(DFS)による全探索もしくはbitDPで解けそうです。コンテスト中にはDFSによる方法しか思いつかなかったです。
スタートする街を全て試して同じ街を通らないようDFSすると、計算量はとなります。なお、DFSを楽に実装できる再帰関数だとTLEが出てしまったため、再帰関数ではなくオイラーツアーで実装しました。
bitDPだと計算量はとなります。計算量的にはこちらの方がよいですが、実装にはやや慣れが必要かもしれません。
DFSで解く場合のコード
def solve(N, M, G): def dfs(start): lifo = [(~start, 0), (start, 0)] costs = [-1] * N costs[start] = 0 used = set() while lifo: u, c = lifo.pop() if u >= 0: used.add(u) for v, w in G[u]: cc = c + w if v not in used: lifo.append((~v, cc)) lifo.append((v, cc)) costs[v] = max(costs[v], cc) else: u = ~u used.discard(u) return costs ans = 0 for start in range(N): costs = dfs(start) ans = max(ans, max(costs)) return ans def main(): N, M = map(int, input().split()) G = [[] for _ in range(N)] for _ in range(M): u, v, w = map(int, input().split()) u -= 1 v -= 1 G[u].append((v, w)) G[v].append((u, w)) ans = solve(N, M, G) print(ans) if __name__ == "__main__": main()
bitDPで解く場合のコード
from itertools import chain def solve(N, M, G): dp = [[-float("inf")] * N for _ in range(1 << N)] for u in range(N): dp[1 << u][u] = 0 for V in range(1 << N): for u in range(N): for v, w in G[u]: if (V >> v) & 1 == 0: VV = V | (1 << v) dp[VV][v] = max(dp[VV][v], dp[V][u] + w) ans = max(chain.from_iterable(dp)) if ans == float("inf"): ans = -1 return ans def main(): N, M = map(int, input().split()) G = [[] for _ in range(N)] for _ in range(M): u, v, w = map(int, input().split()) u -= 1 v -= 1 G[u].append((v, w)) G[v].append((u, w)) ans = solve(N, M, G) print(ans) if __name__ == "__main__": main()
D問題: President
問題の概要
個の選挙区において、番目の選挙区には人の高橋派と人の青木派がおり、多数派がその区の議席を全て獲得する。選挙区全体として過半数を獲得した方が選挙に勝利するとき、高橋君が勝利するためには青木派から高橋派に最低何人を鞍替えさせる必要があるか。
問題の考察
ナップサック問題と同じ構造の問題であるため、動的計画法(DP)で解けそうです。番目の選挙区までをみたときに議席だけ獲得するのに鞍替えが必要な最少人数をとすれば、状態遷移を表せそうです。
区で鞍替えを考慮する必要がないの場合、と配る緩和式を書けます。
それ意外の場合、区で議席を獲得するにはだけ鞍替えさせる必要があるため と、議席を獲得しないならと、配る緩和式を書けます。
状態としてとを持つため、の計算量でこの問題は解けました。
コード
考察では状態としてを持たせていますが、なくても解けるため省略して解いています。
def solve(N, X, Y, Z): dp = [float("inf")] * 200001 dp[0] = 0 for x, y, z in zip(X, Y, Z): for m in range(100000, -1, -1): if x > y: dp[m + z] = min(dp[m + z], dp[m]) else: sw = (x + y) // 2 + 1 - x dp[m + z] = min(dp[m + z], dp[m] + sw) majority = sum(Z) // 2 + 1 ans = min(dp[majority:]) return ans def main(): N = int(input()) X = [] Y = [] Z = [] for _ in range(N): x, y, z = map(int, input().split()) X.append(x) Y.append(y) Z.append(z) ans = solve(N, X, Y, Z) print(ans) if __name__ == "__main__": main()