PAST202112-OPEN

MN以外の88点。最近のPASTは面白い。途中で雪が降ってきた(外の雪かきの音で知った)。

A - アトラクション/Attractions

自分で書いた雑CSSのせいで問題文が見えず少し時間をロス。以上と以下であることに注意して書く。簡単。

B - 穴の開いた硬貨 / Perforated Coin

いきなりこの考察難度の高さ。客がどんな出し方をしようが(例えば40円のものを買うのに140円出すとか無駄な出し方をしても)単に差額を最小枚数で出すだけ。使う50円玉の枚数は、差額を100で割ったあまりを50で割って切り捨てればいい。

C - 最速正解者/Fastest Answer

FAを記録していく(上書きをしないように)。

D - 試験/Examination

stable_sortを使う。

E - キーボード/Keyboard

同じ手を使うかの判定が、どう書いたら楽か悩む。配列で左手に相当する部分だけ値をセットしておきそれを参照するとかも考えたが、結局 '1' <= c && c <= '5' のように書いて前回と比較した。

F - 将棋のように/Like As Shogi

ベタ書きしようかと思ったけどやっぱ大変そうなのでグラフ構築。BFSで新しい場所に来た回数をカウント。

G - 連結/Connectivity

ちょっと驚く制約。O(N^2*Q*α(N)) でまあ間に合うけど「Pythonで通るのかな」とか思う(普段これを思うのは計算量的に嘘解法な疑いがあるときなので(しかし定数倍の軽い10^8なので100ms))。

H - 最長非共通部分列/Longest Non-common Subsequence

LCSさすがに書いたことあるだろと思ったけど短時間では見つけられず。今調べるとEDPCのFだった。当時の復習とほぼ同じコードを今回も書いた。なんか本家より簡単そうに見えるのに結局同じDPになるんだな(意外だった)。頑張って正面から考えていったけど、結局DPになり、いつもの形になった。貰うDPをするとき、最後同士がペアになる場合は簡単で、そうでないなら少なくとも片方はペアにならないということなので片方ずつ持ってくる。これでどんなペアの作り方に対してもDPの経路が存在する。なんか当時はけっこうてきとうにやってたな。

I - 直通エレベーター / Direct Elevator

ダイクストラ法。辺の数を抑えるにはどうしたらいいか。階段を使うのはエレベーターが止まる階と1階とN階を行き来するときのみで、対象の階は階段で順番につながっているのでその間だけ辺を張ればいい。座標圧縮してグラフ構築。階段のコストが圧縮前の値で決まったりして気持ちのいい座標圧縮。ダイクストラで辺のコストが10^18だと心配になるが、どこかの階への最短距離(階段があるので10^18以下)に10^18足されるだけなので2*10^18まで扱えればよく、問題ない。

J - 回転と反転/Rotate and Reverse

テキトーにやってたらサンプル以外全部WA。これまでの問題もそうしてきたがたまたまみんなACだっただけ。簡単な処理で解けることは明らかだが、操作の種類が多くてかなり怖い見た目だった。早いとこACだけ取ってしまいたいが、わからないので一旦飛ばした。

最後までやって、JLMNOの中で解けそうなのがこれしかなくなり、30分くらいかけてやった。マス目を区別し、(0, 0)と(1, 0)と(0, 1)がどこへ行ったか管理する。これはまあ簡単。マス目の状態は元のやつで管理する((0, 0)がどこへ行ってもそこが塗られたらメモリ上のs[0][0]を変更する)。さっきは逆回転させていたが、ちゃんと逆変換を考える。(0, 0)が今そこにあるなら、与えられた(x, y)は元の座標でどこなのか。たくさんの混乱や勘違いを経て、手で作った撃墜ケースとサンプルが通り提出してAC。

K - Gas Station/ガソリンスタンド

これが最後の簡単な問題だった。各ガソリンスタンドからBFSするだけ。

L - 嘘つきな生徒たち/Honest students

嘘が絡むと問題文を読む難易度が上がる。要するに、最小で何個 A_i を変更すれば狭義単調減少にできるかという問題。LISっぽい。狭義単調減少でなければならないのは、A_i を A_i + i で置き換えればいい。得点が0以上P以下でなければならないというのが難しい。

i を足したときの状態を図に描いて、ここで広義単調減少ならいいので、よく見るとLIS(の逆)にN-1以上P以下の値しか含まれていなければ他の値を変更して条件を満たすようにできる。逆にそうでない値が含まれていたら不可能なので、そのように値をフィルターしながらLISをやればいい。LISはいつものを貼ったので、upper_boundへの変更で広義単調にして(ここ何も考えてない)、符号反転で単調減少に対応させた。

M - 名前の変更/Deadnames

いかにも平衡二分探索木という見た目。しかし持ってないので他のアプローチを考える。出現する名前を全部先読みしてソートして番号に変換してBITに載せればいいんじゃない?BIT上の二分探索でX番目の人の名前もわかる。が、その名前の中で指定番目の人がだれかがわからない。しばらく考えてわからなかったので、平衡二分探索木がないと無理な可能性も考え他の問題を優先した。必須なのは今まで出会ったことない気がするのでそうだとしたら嬉しいが。どーなんだろね。(この問題だけ自力で解けておらず解説もまだ見てない)

(追記)解説を見たら、平衡二分探索木は予想通りだけど、トライ木の解法もある。いや無理でしょと思いながら解説ページを閉じて少し考えると確かにできる。いやこれは思いつきたかった。なんかだいぶ実装に時間かかったけどトライ木でAC。今回も平衡二分探索木は実装する気が起きなかったよ(かつて平衡の工夫のないやつは実装経験があり、まああれでさえ実装も定数倍も重かったからなあ)。ただ、またトライ木の理解が広がった。

(追記)コメントで教えてもらった平方分割でAC。ソート済みの列をsqrt(N)個くらいに等分しておけばそりゃあ愚直に挿入削除をやってそこそこ高速だけど、言われなきゃ全く発想にない。前も思ったけど、自分はよほど頭が固いのか、あるいは平方分割を何もわかってない。挿入とかを繰り返して個数が偏ってきたとき愚直に再構築しても、挿入sqrt(N)回がO(N)で再構築がO(N)なのでオーダーが悪化しない。ほえー。実装はかなり時間かかったけど初めてだからまあこんなもん。

N - 共通部分 / Intersection

なぜ凸なのかがわからないが、凸だとけっこうパターンが限られるのか?交差したところを新たな頂点として、ふたりでぐるっとやればいいか。いや、突き抜けて交点が4つになることもある。ていうか完全に含まれている場合もあるじゃん(交点がなくても答えが正になりうる)。これは無理。

ちょっと考えていた、原点を頂点に持つ2つの三角形の共通部分の面積を求める方法に戻ってきた。原点から見て頂点が斜め上にあることが保証されるよう斜め上に少し平行移動させておく。手で色々試すと、やはり1つの頂点を共有する三角形同士だと考えやすい。交点と相手の三角形の内部にある頂点を求めて、ソートして面積を普通に求めればよさそう。交点はライブラリがある(線分の外は交点とみなさないように改造)。内部判定は、そこを中心に三角形で面積を求めて符号が変わることがなければ内部(2頂点がその頂点から見て反時計回りに並んでいるとは限らない(元の多角形ではないので)ことに気づかずはまった)。

バチャ終了。ソートしたら符号の情報が消えることに気づいてなかった。てきとーだけど元の2辺の符号の掛け算っぽいと予想し手で試してそれっぽいので、それで実装。苦労のデバッグで、反対側の線分がスカってるケースを処理してないことに気づき、まあ色々あってサンプルが通り、AC。自信はない。

O - ペアスコア/Pair Score

各ペアの寄与を考える。一つのペアについて、差がWなら置き場所はN-W通りで他の数の置き方が(N-2)!通り。B_iが小さいので、各値が何個あるかカウントして、あとは九九の表みたいのを斜めに足したものが高速に求められればいい。しかしわからない。この問題特有の性質を使うのかとも思ったが、そうなるとS_iまで絡んでくるので余計無理そう。

内積だよなあとか思ってたら、FFTを思い出した。FFTに気づくときと気づかないときの違いは何なんだ。NとB_iの種類数を混同して(添え字が繊細なこともあり)大変だったが、まあatcoder::convolutionを使うだけの楽な問題ではあった。