ABC270の振り返り(A, B, C, D)
振り返りです。
A - 1-2-4 Test
やってみよう!
A
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try
配点が1, 2, 4なんて都合よくあるか!と言いたくなりますが、2の累乗で特定の項目のフラグを管理してその合計値で表現するなんてことはままあったりします。例えばLinuxのファイルのパーミッションとか。(参考:パーミッションを表す数値
直近だとABC264のD問題で使ったBITなど、ビット演算を用いたアルゴリズムも意外にある(はずな)ので基本を抑えておきたいところですな。
結果:#35092854
try2
from operator import or_ as bo def A(*arg): print(bo(*arg)) A(1, 2) # 3 A(5, 3) # 7 A(0, 0) # 0結果:#35769287
自由欄
B - Hammer
やってみよう!
B
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try(コンテスト中)
このような単純な場合分けの問題が結構苦手です…
def B(X, Y, Z): if abs(X) > abs(Y) and X * Y > 0: if Y * Z > 0: if abs(Y) < abs(Z): return -1 else: return abs(X) else: return abs(Z) * 2 + abs(X) else: return abs(X) test_all(B)結果:#35101624
try(振り返り)
振り返っても大体同じになりました。
def B(X, Y, Z): if abs(X) < abs(Y) or X * Y < 0: return abs(X) else: if Y * Z < 0: return abs(X - 2 * Z) elif abs(Z) > abs(Y): return -1 else: return abs(X) test_all(B)結果:#35788168
解説
解説でも大体一緒なんですが、壁を壊す場合はabs(Z) + abs(X - Z)
でまとめられるんですね!
どうやったらこんな発想ができるんだろう…🤔
自由欄
C - Simple path
やってみよう!
C
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try
コンテスト中の提出が以下(BFS)。
DFSでの実装でRE
が出たり(後述)、BFSで経路を保存する方法を調べたりして時間掛かりました…
結果:#35130541
解説を見る
元々DFSの実装に自信無かったので、コンテスト中のデバッグは諦めて上のBFSに切り替えました。
解説がちょうどDFSなのでPythonで書き換えたのが以下。
結果:#35811853
REやんけ!!!!!!
なぜDFSでREになるのか?
ググったところ、どうやら原因はsys.getrecursionlimit()とのこと。
この制限は Python プログラムが無限に再帰し、C スタックがオーバーフローしてクラッシュすることを防止するために設けられています。
sys.setrecursionlimit(limit)で設定を変更できます。
import sys sys.getrecursionlimit() def recur(n): '''再帰コール確認''' print(n, end=" ") if n: recur(n - 1) sys.setrecursionlimit(50) # 40回ぐらいでRecursionErrorが出る recur(40)今回の問題では$1 \le N \le 2 \times 10^5$ で頂点を辿る度に再帰呼び出しを行うので、sys.setrecursionlimit(2 * 10 ** 6)
をコードの最初に挿入してみました。
すると…
結果:#35811986
TLEやんけ!!!!!
なぜTLEになるのか?
はい、またググりました。どうやらPyPyは再帰コールが遅いらしいです。
という訳でPythonで提出してみました。
すると…
結果:#35812008
ようやくAC😇
小括
まとめると、「recursionlimit」と「PyPyで再帰コールが遅い」という二重のトラップが仕掛けられていた訳ですね…
というか、この2つを回避すればコンテスト中に最初に提出したDFS(以下)でもACできてたというオチ。
結果:#35812113
再帰コールせずにDFSの実装
最後に表題の件。
再帰版のDFSの実装ってグローバル変数をいじるところが前からイヤだったんすよね😩
今回Atcoderでの2つのトラップを知り、基本的に使わないという方針を採ることにしました。
ただ、どうしてもDFSを使いたい場合(ってあるのか?)にどうするか。
再帰を使わず実装すればいいじゃない!
という訳で以下。BFSでのキュー(FIFO)をスタック(LIFO)に変えるだけです。
結果:#35921859
いまいちDFSとBFSの使い分け、というかDFSの使い所が分かってないです…
BFSだったら探索だけじゃなくて最短経路にも使えるから、基本BFSでいいんじゃないかなぁ
自由欄
D - Stones
やってみよう!
D
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try(コンテスト中)
コンテスト中に想定誤解法のようなコードを書いて、想定通りWAとなりました…
from bisect import bisect def D(N, K, A): ans = 0 i = 0 while N >= A[0]: get = A[bisect(A, N) - 1] if i % 2 == 0: ans += get N -= get i += 1 return ans test_all(D)結果:#35133986
解説を見る
「最適な行動」系の問題はまだ全然イメージが掴めないのでさっさと解説を見ます。
要点は以下の通り
$DP[n]$ = 石が$n$個残っている状態からゲームを始めたとき、先手が取ることのできる石の個数
遷移:$DP[n] = \max{A_i + (n - A_i) - DP[n - A_i] \;|\; A_i \le n}$
遷移の意味:先手番が$A_i$個の石を取ったとき、最終的に取れる石の個数は、$A_i$+「石が$n-A_i$個残っている状態からゲームを始めたとき、後手が取ることのできる石の個数」なので、これを全ての$i$について調べて$\max$を取ったものが求める値
実装例をPythonで書き換えたのが以下。
めっちゃ短いです…
が、「石がn個残っている状態からゲームを始める」とか「後手が取れる石の数を引く」とかいう発想ができないとそもそもロジック立てられないんですよね…
勉強になります😇
結果:#35924245
自由欄