AtCoder Beginner Contest(ABC) 088 レビュー
2/18に行われたAtCoder Beginner Contest (ABC) 088のレビューです。
問題はこちらのAtCoder公式からどうぞ。
A - Infinite Coins
N÷500の余りが一円硬貨で払う必要のある額。
x=N%500
として、x<=A
ならYes
、違うならNo
。
B - Card Game for Two
「残っているカードのうち、数が最大のものを取る」ことが双方にとっての最適な戦略。
C++なら降順にsort
したりpriority_queue
に入れたりして、先頭の要素から順番に取っていけばいい。計算量はO(N log N)
sort
priority_queue
が無い言語でも、カードの数の情報を配列に保持して、「要素のうち最大の数を得点に追加」→「その要素を0に書き換える」という操作を繰り返せばACできる。計算量はO(V^2)
。
C - Takahashi's Information
diff(1,1)=c(1,1)-c(2,1)
、diff(1,2=c(1,2)-c(2,2)
,という風に、diff(x,y)=c(x,y)-c(x+1,y)
と定義する。
すると、情報が正しいときは、少なくともdiff(1,1)==diff(1,2)==diff(1,3) && diff(2,1)==diff(2,2)==diff(2,3)
が成立する。
上下方向の差の他に、左右方向の差も同じように計算して、条件が成立することを確かめる。
間違えても6重ループなんて実装したらダメだよ!
D - Grid Repainting
(1,1)
から(H,W)
までの最短路以外のマスを塗りつぶせばスコアが最大になる。
つまり、「初期状態の白マス数」-「最短路を通る時に通過するマス数」
が答え。
「初期状態の白マス数」は、単に'.'
を数えるだけで良い。
「最短路を通る時に通過するマス数」は、
struct{ int h //h方向の位置 int w //w方向の位置 int grid //通過マス数 }
という感じに構造体を作って幅優先探索をかけて、(H,W)
に到達したときのgrid
の最小値を求めれば良い。
到達済みのマスを再度探索しないように、bool
型の配列などで到達済みかどうか管理しておくこと。計算量はO(H*W)
。
まとめ
C問題でアホコード書いたり、D問題で深さ優先探索のコードを書いてしまったりして時間をロスしてしまいました。
でも「初めてWA無し全完」「全完タイム自己ベスト」「パフォーマンス自己ベスト」を達成できました。
しかーし。
グフォァァ(死) pic.twitter.com/UkF23tt0lx
— Arc@競プロ (@arcslab123) 2018年2月18日
こうなったんですけどね。惜しかった。
次回は絶対に緑レート行きます。AGCですが頑張ってパフォ稼ぎたい。
JOI終わってしまいましたが、やっぱり競プロは面白いですね。今日はこの言葉を書きたかった。
JOI2017/2018 本選のレビューとか教訓とか、今後の話とか
さて、第17回 JOI(日本情報オリンピック)2017/2018 本選が終わりました。今回はそのレビューです。
あと、教訓・来年度JOIerへのアドバイスとかもあります。
コードレビュー
110点しか取れなかったので、あまり参考にならない。僕の思考ルートを追いかけて楽しんでください。
A ストーブ
最近の本選は累積和・いもす法を最初の問題で使ってきたので、今回もそうかと思ったけど違うっぽい。
ということはDPか。とりあえずこんな感じでDPテーブルを定義してみる。
dp[a][b]=a人まで考慮して、b回マッチを使ったときの最小時間
すると、
dp[a][b]=min{dp[a-1][b]+(a-1人目の人が来てから、a人目の人が来るまでの時間),dp[a-1][b-1]+1}
で求められる。minの前者は「ストーブをつけっぱなしにした場合」、後者は「ストーブを消してつけ直した場合」に該当する。
しかし、ここで問題発生。ACするにはdp[100000][100000]
を用意する必要があるが、そんなにメモリ容量がない。
dp[a][b]
の[b]の方は、b
とb-1
の情報だけ持っておけば漸化式を回せるので、dp[100000][2]
にしてメモリ容量圧縮を図った。でもTLE。
そもそも、計算量がO(N*K)=10^10
なのでメモリ容量があってもAC不可能。このことに気づいたときには1時間を過ぎていた。
一度コードをリセットして、「つけっぱなしにした場合の時間」から「消しておいても良い時間の区間」をできるだけ多く引く作戦にした。
「消しておいてもいい時間の区間」は、a人目が来る時間とa+1人目が来る時間をそれぞれtx
ty
とするとty-tx-1
で求められる。
これを全てのaに対して求めた後、降順ソートをかける。
マッチがk本あるなら、途中で消して良い回数はk-1
回。「つけっぱなしにした場合の時間」から、先程降順ソートした「時間の区間」を上から順にk-1
回引いていってAC。
long long
が必要であることに注意。計算量はソートがボトルネックとなってO(N log N)
。
B 美術展
なんかもうパニクっててBFSしてました。10点。
美術品それぞれに対して「入れる」「入れない」を判定するだけ。O(2^N)
。
「その美術品を入れることによってS-(Amax-Amin)
の値が減少するなら打ち切る」って感じで枝刈りしてみようとしたが失敗。ここでタイムアップ。枝刈り可能なのかすら怪しい。
解説を聞いたところ、累積和を使うのはこのB問題だった。
ここからは駆け足で。
C 団子職人
DPかなぁ……と思ったけど実装せず。
解説はDPでしたが、当日解説では理解しきれず……。
D 定期券
なぜワーシャルフロイドを思いつかなかったのか。あのアルゴリズム好きだったのに。(なお部分点)
たぶん、この問題を解いていたほうが点を稼げた。
E 毒蛇の脱走
高速ゼータ変換、かっこいい。
解説時のDEGwerさんのギャグセンスが高くて面白かった。
コードレビュー編はここまで。多分この後がメインコンテンツ。
教訓編
JOIに参加して得た教訓を書いてみる。来年度以降のJOIerのアドバイスになれば幸い。
計算量見積もりはちゃんとしよう
制限時間とデータ量と問題ジャンルが分かれば自ずと解法が決まるような気がする(実装できるとは言ってない)
— Arc@JOIお疲れ様でした (@arcslab123) 2018年1月24日
こんなこと言ってるくせに何やってたんだか。
100,000,000(
=10^8
) 非常にシンプルな処理でない限り厳しい。(蟻本P20より)
とのこと。落ち着いて考えれば大体の計算量オーダーがわかる、と思う。
部分点戦略を考えよう
AtCoderだと部分点を得られる問題が少ないので、「ACかTLE(MLE)か」の二択になる。
しかし、JOIだと想定解より大幅に計算量が多くなっても(たまにO(N!)
でも)点がもらえたりする。
今回の問題は全て、全探索すれば小課題1あたりで点がもらえた。
ただ、一度「妥協したコード」を書いてしまうと、そこから想定解コードを作るには結構時間がかかる。最悪、コードをリセットしたほうが早い場合もある。
もちろん全課題ACしたほうが嬉しいが、残り時間なども考えて「この問題はしばらく粘ろう」「この問題は妥協しよう」といったことを検討するべき。
(追記 2018-02-16)
二完+(団子職人の課題2まで or 残り三問を全探索で部分点)で本選通過かな
— Arc@競プロ (@arcslab123) 2018年2月12日
個人的には2完+定期券ワーシャルフロイドの255点の方が可能性高かったかもしれない
— Arc@競プロ (@arcslab123) 2018年2月12日
まあ2完できなかった時点でア
JOI公式から本選Aランクボーダーが開示されたが、2完でボーダーに乗せるには多分上記の3パターンが現実的かと思われる。1完+部分点で無理やり本選通過しようとすると、
「問題AをAC(100点)+問題Bを課題3まで(50点)+問題Cを課題2まで(33点)+問題Dを課題3まで(55点)」
の、合計238点コースが妥当だろうか。けっこう非現実的ではある。
前日の睡眠を良質にしよう
今回宿泊した部屋は温度調節が大変だった……。
まあ、今回の宿泊場所に限らず、多くの人がホテルでは眠りづらいはず。
耳栓を持ってくる、加湿するなどして眠りやすい環境を作ろう。
競技中のエネルギー摂取手段を手に入れよう
だんだんネタに走っているような気がするが、結構重要。
今回は「粉っぽいモノ(ポテチなど)以外の菓子類を持ち込み可能」だったので、競技中にチョコレートを食べてた。(去年は違ったとの情報も耳にしているので、最新の規定はJOI委員会に尋ねてみることをおすすめします)
7時ぐらいに朝食を食べた後、9時から13時まで4時間頭を使ってたら、脳がエネルギー切れを起こすのは自明(僕だけか?)。
何か持っていったほうが良い。エネルギー切れ対策の他にもリフレッシュにもなる。
あと飲み物。魔剤を飲んでた人もちらほら。僕は飲めないが。
カフェイン取りすぎるとトイレが近くなるので注意。
体調管理のプロになろう
実は土曜日、若干熱っぽかったかも。体温測ってないので本当か分からないが。
無理して精進して体調崩したら元も子もないから気をつけよう。
あ、咳とか鼻水とかは無かったので、カゼでもないしインフルでもないと思います。ってかインフルだったら今ごろ倒れてる。
なにか思いついたら追加するかもしれないです。
まとめ
最初で最後のJOI本選だったわけですが、「もっと早く競プロやってればなぁ」と後悔しております。
実は、予選通過時にはまだ「競技プログラミング」ってのを知りませんでした。通過直後に知りました。
プログラミングは年齢より経験年数の方が遥かに重要だと思うので、まだJOI出場チャンスが残ってる&たくさん経験値を稼げる中学生・高1競プロerが正直言って羨ましいです。
僕も頑張ってACM-ICPCに出たいですねぇ。そのためには受験を突破しなければならないわけですが。
でも、1年以上ブランクを空けるのが余りにももったいない気がするので、これからもAtCoderのコンテストにはたまに参加したいと思います。
ということで、帰宅後に勢いで書いたJOI本選レビューでした。今度のリアルイベントはCombNafかな?
更新情報
2018-02-16 タイトルと本文をちょっとだけ変更、部分点戦略について追記
AtCoder Beginner Contest(ABC) 084 レビュー
今回は12/30のAtCoder Beginner Contest(ABC)084 についてです。
あ、前回のABC083ですが……全然ネタがないので飛ばします……
A - New Year
2日は48時間。48-M
すればOK。
B - Postal Code
ハイフンが見つかる or 文字列の末端まで到達する
まで文字数をカウント。これを二回繰り返す。
最初のカウント数==A and 二回目のカウント数==B
なら条件を満たす。
C - Special Trains
まず思いついたのは、
駅iに到着した時間=arrive[i]
として、arrive[i]<=S[i]+F[i]*n
となるような最小のnを見つける(次に乗る電車の到着時刻を計算)。arrive[i+1]=S[i]+F[i]*n+C[i]
これを終点に到着するまで繰り返す。
というもの。
だけど、このアルゴリズムだと(2)の処理にめちゃくちゃ時間がかかる。
そこで以下のように改良。
駅iに到着した時間=arrive[i]
として、駅iに到着してから次の電車が来るまでの時間=lag[i]
とする。もし
arrive[i]-s[i]<0
なら(まだ最初の電車が動いていなかったら)arrive[i+1]=S[i]+C[i]
、そうでなければarrive[i+1]=S[i]+lag[i]+C[i]
これを終点に到着するまで繰り返す。
で、lag[i]
の計算方法は、
lag[i]=F[i]-((arrive[i]-S[i])%F[i])
もしlag[i]=F[i]
ならlag[i]=0
にする
という感じ。
これなら計算量少なくて済む。
D - 2017-like Number
1つのクエリ(l[i],r[i])
に関して、l[i]<=n<=r[i]
を満たす全ての奇数nをチェックするのは明らかに無駄。同じnに対してチェックを何回も繰り返すことになる。
たとえばクエリが(l,r)=(5,9),(3,11)
なら、5,7,9
は二回チェックしていることになる。時間が足りない。
ということで、累積和を使うことにする。
rui[i] = [1<=n<=i*2+1を満たす奇数nのうち、2017に似た数の個数]
と定義すると、クエリ(l[i],r[i])
に対して
left=l[i]/2 , right=r[i]/2
と定義し
left==0
ならrui[right]
を出力そうでないなら
rui[right]-rui[left-1]
を出力
以上の処理を実行することで解答可能。
で、「2017に似た数」をどうやって判別するかですが……「エラトステネスの篩」でググってみてください。僕は使いませんでした。完全に嘘解法です。
あと、この問題解いている時にコンパイラのバグ(?)に遭遇。プログラム上では書き込んでいないグローバル変数に勝手に値が書き込まれてました。AtCoderのシステムにソースコード投げたらあっさりACしましたが。
まとめ
- 初めてABC4完しました。やったね。
- でもARC上位陣が沢山いたからか、パフォーマンスはそこまで良くもなかったです。自己ベストだけど。
- 茶レートにあがりました。
- デバッグ用の出力はちゃんとコメントアウトしましょう。これのせいで1WA。
- 実戦で累積和使えたので良かったです。
- エラトステネスの篩ぐらい知っておこう……。
それでは皆様、2018年もプログラミング頑張りましょう。良いお年をお迎えください。
AtCoder Beginner Contest(ABC) 082 レビュー
今回は12/16のAtCoder Beginner Contest (ABC)についてです。
今回が初参戦となります。
問題はAtCoder公式をご参照ください。
A - Round Up the Mean
int型の割り算は小数点以下切り捨てなので、a+bに1足してから2で割れば切り上げた平均値が出てくる。
なぜか僕は(a+b)%2
の値を見てから処理していましたが。
B - Two Anagrams
Cしか触ったことのなかった身にとっては、C++の ちからって すげー! と思った問題。
stringコンテナのs,t
を用意して文字列を代入し、sort(s.begin(),s.end());
sort(t.begin(),t.end(),greater<char>());
を実行する。
すると、sの中身は昇順、tの中身は降順にソートされる。
で、if(s<t)
がtrue
ならYes、false
ならNoを出力。
ソートアルゴリズム書く必要もなし。strcmp
使わなくてOK。そう、C++ならね
C - Good Sequence
数字nがm回出現したとき、
-n>m
ならm
個消す
-n<m
なら`m-n'個消す
というのを、全てのnに対して確認する。
この時、数字nが何回出てきたかをカウントする配列count[]
が必要になってくる。
しかし、count[]
の長さは普通に考えると109必要。メモリ足りない。
そこで、数列の長さNが105以下に制限されていることに注目。
105を超える数字nはn回以上出るわけがない。つまり、常にn>m
を満たす。
ということで、105を超える数字が出てきたら無条件で消すことにする。
そうすれば、105以上の数字をカウントする必要がなくなるので、count[]
の長さは105で十分。計算量はO(N)で解ける。
……模範解答は連想配列使ってましたね。研究が必要そうです。
D- FT Robot
最初は深さ優先探索して、その後メモ化したんですが、向き情報をDPの情報に入れていなかったので撃沈。タイムオーバー。
しかも、この方法ですらTLEになるらしい。O(N3)ならそこそこ速くない……?
x軸方向の移動とy軸方向の動きを分離すればO{N2}で解けるそう。
最初の提出時にデバッグ用のprintfを貼りっぱなしにしていたのは秘密。
まとめ
今回はstringの書式確認やらDPの凡ミスやら、時間の無駄遣いをしすぎました。
次回は変なミスしないようにします。
はじめまして
Arcと申します。
競技プログラミング、写真といったテーマについて書いていこうと思います。
ブログの目的としては
- 自分の思考の記録
- 文章力を上げる
- 競プロなどの初心者の手助け
- 覚書
こんな感じでしょうか。
とりあえず最初のうちはJOI本選に向けた勉強の記事でも書いていこうかと思います。
よろしくお願いします。
Arc@競プロ (@arcslab123) | Twitter
Twitter(写真垢)::
Arc@写真 (@arcslab_photo) | Twitter
Arcさん(@arcslab_photo) • Instagram写真と動画