ABC261の振り返り(A, B, C, D)
テスト関数定義↓
# 縮小表示 def test_all(f): for i, data in enumerate(eval(f"test_data{f.__name__[0]}")): exp = data["out"] ans = f(*data["in"]) result = "AC" if exp == ans else "WA" print(f"{i+1} {result}: expected: {exp}, output: {ans}")A - Intersection
やってみよう!
A
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try
なんか、A問題の割にむずくないですか…
問題の趣旨は2直線の重複範囲を求めよ、ということなんですが、場合分けが面倒です。
とりあえず始点にだけ着目して、先に始まる線をfirst
、もう一方をsecond
とします。
すると、first
の終点とsecond
との関係は以下の3パターンに分けられます。
first
の終点がsecond
の始点より小さい
first : |---|
second: |------|
first
の終点がsecond
の始点より大きく、終点より小さい
first : |-----------|
second: |------|
first
の終点がsecond
の終点より大きい
first : |-------------------|
second: |------|
3つの場合それぞれで回答すればよい、という訳で以下。
def A(L1, R1, L2, R2): first, second = (L1, R1), (L2, R2) if L1 > L2: first, second = second, first if first[1] < second[0]: return 0 if first[1] <= second[1]: return first[1] - second[0] second[1] second[0]< py-cell> test_all(A)結果:AC 提出 #33490865 - AtCoder Beginner Contest 261
自由欄
解説を見る
結論から言えば、答えは $max(0,min(r1,r2)−max(l1,l2))$ で求める事ができ、これを知っていれば早いと思います。
まじか。
def A(L1, R1, L2, R2): return max(0, min(R1, R2) - max(L1, L2)) test_all(A)結果:AC 提出 #33490904 - AtCoder Beginner Contest 261
まじだった…
自由欄
try2
上の解法がスマートすぎて、なんだかむしゃくしゃして以下のような回答を考えました。
うん、きちゃない🤮
結果:提出 #33491253 - AtCoder Beginner Contest 261
自由欄
B - Tournament Result
やってみよう!
B
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try
これは設問通り実装すればいいですな。
set
の妥当な使い道です笑
結果:提出 #33491823 - AtCoder Beginner Contest 261
自由欄
C - NewFolder(1)
やってみよう!
C
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try
時間にシビアな問題です。
普通に考えたらdict
に入れていけばいいんでしょうけど、それでいいのか…?
もっと高速化しなければいけないのでは!?
という訳で二分探索するようにしてみました。
from bisect import bisect_left as bisect def C(N, S): St = ["{"] C = [None] ret = [] for s in S: idx = bisect(St, s) if s == St[idx]: C[idx] += 1 ret.append(f"{s}({C[idx]})") else: St.insert(idx, s) C.insert(idx, 0) ret.append(f"{s}") return "\n".join(ret) test_all(C)結果:TLE... 提出 #33466247 - AtCoder Beginner Contest 261
自由欄
try2
やっぱりdict
なのか!?
でもin
演算子は二分探索より早いのか!?!?
という訳でtry ~ except
で実装
結果:AC 提出 #33468127 - AtCoder Beginner Contest 261
自由欄
try3
in
演算子でも普通に早かった。
結果:AC 提出 #33492321 - AtCoder Beginner Contest 261
自由欄
D - Flipping and Bonus
やってみよう!
C
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try
コンテスト中は30分くらい時間があったのですが、全く分かりませんでした😇
という訳でさっそく解説を見ます。
・・・なるほど、分からん!
とりあえず実装してみたのが以下。(こんな短いんか…)
def D(N, M, X, CY): ''' 解説参照 ''' X = [None] + X b = [0] * (N + 1) for c, y in CY: b[c] = y dp = [[0] * (N + 1) for _ in range(N + 1)] for i in range(1, N + 1): for j in range(1, i + 1): dp[i][j] = dp[i-1][j-1] + X[i] + b[j] dp[i][0] = max(dp[i-1][:i]) ans = 0 for i in range(N + 1): ans = max(ans, dp[N][i]) return ans test_all(D)結果:提出 #33494080 - AtCoder Beginner Contest 261
自由欄
解説を理解する
なにも考えず写経してACを確認したところで、ちゃんと考えてみます。
dp
を返すようにした下のD_debug
関数で確認してみましょう。
dp
は以下のようになっています。
dp[1][1]
からの対角成分が、全ての試行で表が出た場合の各時点での獲得金額となっているのがわかります。
もう一度ちゃんと解説を読んでみると、dp[i][j]
においてj
は「現在のカウンタの数値がjである」ことを表しているとのこと。
ふむふむ。これで対角成分の↘の動きはわかりました。
次に、対角成分より左下の下三角部分を見てみましょう。
この部分でも↘の動きは同様で、i
の通常獲得金額とj
のボーナスを累計していくことは分かるのですが、問題はその始点、つまりdp[i][0]
の値です。
「現在のカウンタの数値が0である」ということは、その回の試行では裏が出たことを表しています。
ここで確認ですが、今考えているのは任意にコイントスの表裏を操作して獲得金額を最大化するといくらになるか、ということです。
そして通常獲得とボーナスの組み合わせによっては、どこかで裏を出すことでそれを達成することができる場合もあるわけです。
さて、i
回目のコイントスで裏を出すことで獲得金額を最大化できるとしましょう。
その場合、i
回目の獲得金額はi - 1
回目までの考えられる獲得金額のうち、最大のもの設定すればよいです。
それをやっているのが12行目ということなんですね。
賢いっ!
実際、このケースでは3回目で裏を出すことで最大金額を得られます。
いや~、なんというか脱帽です。
まとめ
DPの練習しなきゃ…