ABC285の振り返り(A, B, C, D, E, F)
振り返りです。
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 - Edge Checker 2
やってみよう!
A
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try
プラス・マイナスを間違えてWAになった戒めとして記録しておきます…
def A(a, b): if a > b: a, b = b, a if a * 2 == b or a * 2 == b - 1: print("Yes") else: print("No") A(1, 2) # Yes A(2, 8) # No A(14, 15) # No結果:#38044890
振り返り
色々無駄が多いのでやり直したのが以下。
めっちゃ短く書けるやん。コンテスト中、全然頭回ってなかったな…
def A(a, b): print("Yes" if b // 2 == a else "No") A(1, 2) # Yes A(2, 8) # No A(14, 15) # No結果:#38188781
自由欄
B - Longest Uncommon Prefix
問題略
やってみよう!
B
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try
題意を正確に読み取る類の問題。
実装は色々で短くできる余地は多分にありそうですが、こういう問題ではできるだけ忠実に設問に沿ったコーディングをしたほうがリーダブルでバグを防げると思います。
以下コンテスト中の提出。
def B(N, S): S = "0" + S for i in range(1, N): for k in range(1, N - i + 1): if S[k] == S[k + i]: break else: print(k) continue print(k - 1) B(6, "abcbac") # 5 # 1 # 2 # 0 # 1結果:#38050823
自由欄
C - abc285_brutmhyhiizp
問題略
やってみよう!
C
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try
26進数→10進数の変換。
コンテスト中の提出が以下。
結果:#38054498
振り返り
ほとんど同じですが、処理としてはちょっとシンプルになった(、か…?)
def C(S): n = lambda c: ord(c) - ord("A") + 1 CARDINAL = n("Z") ans = 0 for c in S: ans *= CARDINAL ans += n(c) print(ans) C("AB") # 28 C("C") # 3 C("BRUTMHYHIIZP") # 10000000000000000結果:#38249291
でも改めて考えると、10進数の10
は見た通り二桁になるのに対して、設問のIDは26進数で26番目がZ
と一桁で表せる最大の数になるのがなんか不思議。
0
が無いとこうなるのか。
自由欄
D - Change Usernames
やってみよう!
D
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try
グラフの閉路(サイクル)判定問題。だいぶ苦戦しました…
コンテスト中はどうにかBFSで一発ACできましたが、かなり無駄が多い実装だったのでやり直したのが以下。
結果:#38282949
コード量も実行時間も短縮できました。
DSUで解く
解説によるとDSU(Union-Find)でも解けるとのこと。
ABC284のDSUクラスを使いまわしたいところですが、これは頂点として整数が与えられる想定で実装されています。なので前処理としてユーザー名に一意の整数を割り振ることにしました。
結果:#38284930
メイン処理部分がすげぇ短くなった。
ググった範囲だと、DSU(Union-Find)クラスを文字列対応にするか、上記同様ユーザー名にID(整数)を振るかで対応が分かれる感じ。
自由欄
E - Work or Rest
やってみよう!
E
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try
コンテスト中は全く糸口が掴めず時間切れとなりました。
解説 を読んでもよく分からないので、とりあえずPythonで書き直したのが以下。
結果はACとなりましたが、これ13行目のdp[i + 1][j + 1]
への代入なんて1回しか実行しないんだからmax
とる必要なくない?
ていうか値を新たに計算しているのは14行目のdp[i + 1][0]
だけで、あとは右下にコピーしてるだけじゃん。
という訳で短くしてみたのが以下。
from itertools import accumulate def E(N, A): A = [0] + A *B, = accumulate(A[(i + 1) // 2] for i in range(N + 1)) dp = [0] for i in range(1, N + 1): dp = [max(d + b for d, b in zip(dp, B))] + dp return dp[0] test_all(E)結果:#38320849
めっちゃ短くなって無事ACとなりました。
自由欄
解説を理解する
短くはできても、どんな意図でコーディングされたのかが理解できていません。
とりあえずループを回すたびにdpを出力してみます。
前述の通り、新たに計算されるのは配列の先頭要素のみです。
で、結局このdpが持っている数字は何かというと、一週間が[i日, i-1日, ..., 1日, 0日]
の場合の生産量の最大値となっています。(一週間の一日目は休み)
なぜそうなるのかを考えるために、i == 7
のときの計算を確認してみます。
1週間が6日の生産量の最大値 + 1休 = 41 + 0 = 41 〃 5日 〃 + 1休1連勤 = 40 + 10 = 50 〃 4日 〃 + 1休2連勤 = 30 + 20 = 50 〃 3日 〃 + 1休3連勤 = 20 + 30 = 50 〃 2日 〃 + 1休4連勤 = 10 + 40 = 50 〃 1日 〃 + 1休5連勤 = 0 + 41 = 41 〃 0日 〃 + 1休6連勤 = 0 + 42 = 42
という訳で、結局1週間が7日の場合の生産量は最大50ということになるわけですね。
最初にB配列を作り、それを上のような計算で使えるという発想を、とても思いつくことができませんでした…
理解できた今でも、化かされたような気分。DPムズい。
自由欄
F - Substring of Sorted String
やってみよう!
E
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try
せっかくなのでF問題もやってみます。
とは言え全くどうすればいいか分からないので解説を見ます。
問題の難易度が上がってくると『考察力』というか『条件を言い換える能力』がより重要になってきますね…😩
本問では部分文字列を判定する上で実際に$T$($S$を昇順に並び替えた文字列)を作る必要はなく、$S[L:R]$(コード上はS[L:R+1]
)が昇順であること(条件①)ともう一つの条件(条件②)を満たせば「部分文字列である」と判定できることが解説で示されています。
ではそれぞれの条件をどのように判定するかですが、上の解説を参考に(セグ木ではなくBITで)実装したところTLEとなったので、条件①を別の解説(こちらやこちら)を参考に(セグ木ではなくBITで)実装し直したところ、やっとのことでACとなりました。
それが以下。
class BIT: def __init__(self, a): if hasattr(a, "__iter__"): self.size = len(a) self.data = [0] * self.size self.bit = [0] * (self.size + 1) for i, j in enumerate(a): self.add(i, j) elif isinstance(a, int): self.size = a self.data = [0] * a self.bit = [0] * (a + 1) else: raise TypeError() def add(self, i, x): self.data[i] += x i += 1 while i <= self.size: self.bit[i] +="x" i & -i def sum(self, i): '''get sum of data[:i] ''' total="0" while> 0: total += self.bit[i] i -= i & -i return total def get(self, x): res = 0 i = 1 while i < (self.size + 1) / 2: i <<= 1 while i> 0: if res + i < self.size and self.bit[res + i] < x: x -= self.bit[res + i] res += i i >>= 1 res += 1 return res if res >= x else -1 def __getitem__(self, key): return self.data[key] def __setitem__(self, key, value): self.add(key, value - self[key]) self.data[key] = value def __len__(self): return self.size def __repr__(self): return self.data.__repr__() def __iter__(self): return self.data.__iter__() from collections import defaultdict from string import ascii_lowercase def next_c(c): return chr(ord(c) + 1) def F(N, S, Q, queries): S = list(S) # S内に各文字が何個あるかを保持 num = defaultdict(int) # S[i]時点で各文字のS[0]からの合計をBITで管理 abc = defaultdict(lambda: BIT(N)) # Sの任意区間が昇順かを判断するための情報を管理 ## S[i-1]とS[i]が昇順であればS[i]=0、そうでなければS[i]=1 ## is_sorted.sum(l + 1) == is_sorted.sum(r + 1) ならS[l:r+1]は昇順 is_sorted = BIT(N) # 前処理 for i, c in enumerate(S): num[c] += 1 abc[c][i] = 1 if i > 0 and S[i - 1] > c: is_sorted[i] = 1 for q in queries: type, *args = q.split() if type == "1": x = int(args[0]) - 1 c = args[1] # 置き換えられる文字の処理 num[S[x]] -= 1 abc[S[x]][x] = 0 # 置き換える文字の処理 S[x] = c abc[c][x] = 1 num[c] += 1 if x > 0: is_sorted[x] = 0 if S[x - 1] <= 1 c else if x < n - 1: is_sorted[x + 1]="0" s[x>= c else 1 else: l, r = map(lambda x: int(x) - 1, args) # 前処理 c_max = "a" c_min = "z" num_lr = defaultdict(int) for c in ascii_lowercase: num_lr[c] = abc[c].sum(r + 1) - abc[c].sum(l) if num_lr[c] > 0: c_max = max(c_max, c) c_min = min(c_min, c) # 昇順? if is_sorted.sum(r + 1) == is_sorted.sum(l + 1): # 部分文字列? c = next_c(c_min) while c < c_max: if num_lr[c] != num[c]: break c = chr(ord(c) + 1) else: print("Yes") continue print("No") test_all(F)結果:#38445485
昇順の判定をBITで実現できる、というテクニックを知れたのは有益でした。
こういう小技を地道に積み重ねて、条件の言い換えなど『考察力』を高めていこうと思います!