【競プロ典型 90 問】001 - Yokan Party
AtCoder始めました。
という訳で、米田氏が企画した「競プロ典型90問」を解いていきます。
ここでは私の試行錯誤やら解説の理解やら気づきやらをメモしていこうと思います。
競プロ初心者なのでどうか温かい目で見守ってください。
なお、本記事中のPythonコードはブラウザ上で実行可能です。
よろしければ遊んでいってください。
問題
やってみよう!
テストデータ・テスト関数定義 ↓
# 縮小表示 test_data = [ { "in": [3, 34, 1, [8, 13, 26]], "out": 13 }, { "in": [7, 45, 2, [7, 11, 16, 20, 28, 34, 38]], "out": 12 }, { "in": [3, 100, 1, [28, 54, 81]], "out": 46 }, { "in": [3, 100, 2, [28, 54, 81]], "out": 26 }, { "in": [20, 1000, 4, [51, 69, 102, 127, 233, 295, 350, 388, 417, 466, 469, 523, 553, 587, 720, 739, 801, 855, 926, 954]], "out": 170 }, ] def test_all(f): for i, data in enumerate(test_data): exp = data["out"] ans = f(*data["in"]) result = "AC" if exp == ans else "WA" print(f"{i+1} {result}: expected: {exp}, output: {ans}")下のmain0
関数を完成させて「Run」ボタンをクリックしよう!
自由欄
try(1st.)
とりあえず何も参照せずにやってみます。
今の私は全探索以外の武器を持っていないので愚直に全探索です。
よっしゃ、ちょろいぜ!提出!!
結果(1st.)
やはり全探索では無理がある…
解説
答えで二分探索とのこと。
そんな発想、微塵もなかった…
羊羹問題、完全に理解した。
try(2nd.)
def main(N, L, K, data): def is_exist(v): left = 0 cnt = 0 for l in data+[L]: target = l - left if target < v: continue left = l cnt += 1 if cnt > K: return True return False l = 0 r = L while r != l: mid = (r + l + 1) // 2 if is_exist(mid): l = mid else: r = mid - 1 return l test_all(main)結果(2nd.)
無事AC
推敲
ここで想定コードを見ながらもうちょっと考えてみます。
なんか、自分の二分探索のコードがスッキリしないなぁ…
と考えていたら、そのものズバリのご指摘が。
【めぐるのアルゴリズム講座】
— 因幡めぐる@競技プログラミング (@meguru_comp) February 9, 2016
二分探索(整数)の書き方
難しさ:4 pic.twitter.com/LGLbkS0D7l
謎のJK(?)に的確なご助言賜り複雑な心境ですが、とりあえずめぐる式二分探索法に修正したのが以下。
def main(N, L, K, data): def is_exist(v): left = 0 cnt = 0 for l in data+[L]: target = l - left if target < v: continue left = l cnt += 1 if cnt > K: return True return False ok = 0 ng = L while abs(ok - ng) > 1: mid = (ok + ng) // 2 if is_exist(mid): ok = mid else: ng = mid # okは条件を満たす最大の値、 # ngは条件を満たさない最小の値 return ok test_all(main)提出 #32319198 - 競プロ典型 90 問
(結果や実行時間はほぼ変わらず)
追記(2022/06/26)
最近、bisectモジュールの存在を知ったのですが、Python3.10からbisect
内の各関数でkey
引数を取ることができるようになりました。
上のmain
関数を書き換えると以下のようになります。
う~ん、コードが短くなるのはいいとして、直感的には理解しづらいですかね。
取りうる範囲の中でTrue
とFalse
の境目を探すために「答えで二分探索」はそのままです。
内部的にはあくまでも大小関係を比較します。True > False
はTrue
なので、「ソート済み配列」にするためにrange(L, -1, -1)
としています。
現状、AtCoderのPythonは3.8なので(参照)実戦で使うのはまだ先になりそうですが、知っていて損はないでしょう。
追記終わり