PAST202012-OPEN

LMOが解けず。Lは解説を見てAC、MOは終了後に解法を見つけてAC。実装力のなさを感じた。

A - ○✕ゲーム/OX Game

珍しく実装が軽くてよい。答えを"draw"で初期化しておいて、3連続を見つけたらその文字に上書き(5マスなのでox両方が3つ並んでいることはない)。

B - 上書き/Overwrite

愚直にやるだけだが、削除は(計算量を無視しても)やりづらいので別のstringを用意してそこへcでない文字を追加していった。

(解説を見て)std::remove知らなかった。この問題専用関数か?

C - 三十六進法/Hexatridecimal

いつもの。push_backしていって最後にreverseした。0-9とA-Zの場合分けが要るのと、N=0のケースに注意。

D - リーディングゼロ/Leading Zeros

普通にソートして間に合う。が、比較部分を書くのが果てしなく面倒。リーディングゼロを数えて、本体部分の長さを比較、同じだったら上位から比較していき、それも同じだったらリーディングゼロで比較。STLの機能を使えないか考えながら、結局全部自前でやった。きつい。

E - ハンコ/Stamp

「はみ出してはいけないなら移動はできないよね?」と誤読。マス目Sは長方形だが、ハンコの底面は'#'のところだけだ。簡単なのはわかっているのに自分の力ではすぐに書けないもどかしさ。ここでトイレに行って実装方法を詰めた。30*30で実装する。90度回転させながら、20*20通りの位置を試す。判定は、ハンコの底面であってSの障害物のないマスでない位置が一つもなかったらYesとする。後半の問題より時間かかってる。

F - 一触即発/Dangerous Chemicals

全探索できそうだけど実装も実行時間もかなり重そうだ。もう少し考えると、爆発する組み合わせはbitで詰め込めばintで表現できるし、それを使って2^N通りの全パターンについて爆発するか判定しておけばいい。あとは、爆発しないものについて、N通り薬品を足して爆発するかを見て(準備しておいたのでこれがとても楽)数える。混ぜ方kが爆発することは !(~k & f[i]) のように書ける(f[i]で立っている全bitについて~kが0、つまりkがf[i]を含んでいることを表す)のだが、使用頻度が低いので考えないと出てこない。

G - ヘビ/Snake

これまた恐ろしい問題。PASTの前半は全探索が多いのだが、それとわかっていても難しいのだ。スタート地点は高々16通り、移動が高々15回、分岐は最初を除き高々3つ、まあ間に合う(実際ははるかに減りそう)。DFSで、#を消して、残り#数を更新して、通った道を記録しながら。残りが0個になったら成功なので道を出力する。

(解説を見て)角を通るとき次の移動方向は1通りしかないの明示的には気づいてなかった。

H - コンベア/Conveyor

マス(r, c)からBFS。逆なのでややこしいが、有向辺が張られていると考えて反対向きにするだけなのでまあ。'#'でないマスについて、例えば左隣が'#'でないなら、左が'.'か'>'のとき辺を張る。自分が'.'か'<'なら逆向きに(も)張る。

I - 避難計画/Evacuation Plan

ダイクストラとか使う普通のグラフ問題かと思いきや、低いほうへしか動けないというきつい制約がある。村は(番号はバラバラになるが)標高順に一直線上に並んでいるとしてよく、一方向にしか移動できない。このグラフの最短経路長はDPでわりと楽に求まる。避難所から逆に上っていくと考えて、避難所たちを0、他をNで初期化して、何本の水路が要るか求める。

(解説を見て)上の文章を書いているときから薄々気づいていたが、始点が複数のBFSで普通にできるね。自分のコードはちょっと計算量多い気もしてたけど、ソートもなくせるんだね。

J - 長い長い文字列/Long Long String

難しくなってきた。数字xがあったら、そこまでのものをx+1回繰り返している。高々10倍ずつしか増えないので、X(を0-indexedに直したもの)を超えた時点でストップしてその中で具体的な位置を求める。直前の数字のあとに追加した文字数、倍率、Sでの位置を記録しておく(範囲forはインデックスがとれないので使いどころが難しかった)。繰り返しの何回目かは関係ないのであまりをとって、付け足した文字たちの場所ならそのまま出力、そうでなければまたあまりをとっていく。30分以上かかったけど、われながらよく実装できた。

K - 的あて/Pitching

全パターン(2^16通り)についてDPで期待値を求める。16通り投げてみてそれぞれ5パターンが等確率で起こるので平均とるだけ。と思ったら自分自身に戻ってくることもあって面白い(笑いが漏れた)。方程式を解く感じにするか迷ったけど、ループを抜ける確率の逆数だけかかるもんなのでそう書いた。

L - T消し/Collecting T

既視感はARC108B。あのときは全部違う文字だった。今回はサンプル2のようなことが起こる。O(N^2)個の区間を管理して間に合いそうなのでDPは思ったが、左右に分けてしまうと間に挿入するようなやつを計算できない。括弧のように考えてもダメそう。10分くらい考えてMへ。

終了後、わからなそうなので解説を見た。Tの3文字の間が全部消せればいいのかあ。端の文字を除かない場合と、左端と右端を一緒に取り除く場合、一緒じゃない場合。これで尽くされているというのが、特に解説を見てしまったあとだと、なかなかわからない。

M - 棒の出荷/Shipping Sticks

しばらく考えて二分探索が浮かぶ。しかし判定ができない。二分探索を使わず貪欲みたいにできるということもあるのだろうか。上下から押さえられてるので、二分探索するにも貪欲ができない。区間を隣にあげても損しないみたいなのが見えない。深く考えるとあるのかなあ(しんどすぎる)。15分くらいでNへ。

終了後に考えて、やはり二分探索ができそうだ。端からDPで条件を満たすか調べるのは考えていたが、ある場所から見て長さが区間に収まる範囲はしゃくとりでできると気づいて打開できた(これ気づくのかなり難しくない?)。実装が本当にきつい。DP配列はN+2個確保して累積和の形で持つ。二分探索は2以上L+1未満で。

N - 旅行会社/Travel Agency

「N-1本の道路」で木かと思うがどうも木とは限らなそうだしそうなると閉路も出てきて意味がわからない。よく見ると全然そういうことではなかった。左右どこまで行けるかという話のようだし、区間の共通部分はセグ木でできる。ここで、座標圧縮が必要だと勘違いする(今まで実装きついの多かったしね)。セグ木の二分探索できないのでlogが2個付くけど解けそうなので一旦寝かせる意味でOへ。

座標圧縮が要らないと気づいてボリボリと実装。左右への二分探索でやや手間取った。半開区間のどっち側かが左右で変わるんだもんな。解説を見ると別の解き方もあるらしい。更新しないセグ木だったしなあ。

O - 通知/Notification

面白そうな見た目だけど一向に手がかりが出てこない。平方分割も浮かんでたけど最終形が見えなかった。

友達が600人以上いる人は高々600人くらいしかいないはず。少ないのは愚直で、多いのは逆から多い人だけ何回通知を送ったか足していく。カウントの重複や漏れがないか確認するのが大変だった。有向グラフだと思うと考えやすかった。前回までの通知の数をユーザ毎に持っておけば累計を求めて引けばよくなる。コードはそんなに複雑じゃないけど、そこへ至るまでが自分にとっては大変。