ABC372

6完。早解き回を早解きできなかったけど、まあ淡々と解けたのでいいでしょう。今回は、(展開的に)天啓が来るのを待ってただけという感じなのであまり疲れていない気がする。

三者三葉を読んだりけものフレンズタイムシフトを見たりしており1:20くらいから書き始めている。眠い。

A - delete .

'.'でない文字だけ出力していく。久しぶりに1分切ったよ。A問題はこういうのにしてくれ。

B - 3^A

3^20を調べたけど、よく考えると各桁最大で2個必要なので3^10を調べると10^5には届かない。サンプル見ると3^10-1みたいのがある。制約に注意してくださいと書いてあって怖くなる。いやB問題でこの考察難度に加えNの余裕もないって。ちゃんと考えると、3^10は10^5の半分以上はあるので、3^10を1個加えることで2*3^10未満までは表せる。それだと最大21個使ってしまうが、10^5は2*3^10-1より小さいので少なくとも1個は減って20個以下に収まる。これ適当に書けばACはするので、ちゃんと考えた人がタイムで損するやつじゃん。

C - Count ABC Again

位置iから始まる3文字が"ABC"になっているかを判定する関数を書いておく(ここでiの範囲チェックもしておくことでのちの実装がやりやすくなる)。まず、初期状態のSに何個あるか数える。次に、クエリが来るたび、周辺の個数を引いて、Sを更新し、周辺の個数を足す。

D - Buildings

サンプルで出力の末尾が全部0であることを確認。制約から、Hは順列になっている。差分計算みたいのができるとしたら後ろから見ることになりそう。BITとかでできるんだろうと思って考えていくと、微妙にできない。条件を満たすjの候補が、1個左に動くたび現れ、自分より高いのが現れたら消える。ビルの高さを追加するだけでなく、自分より小さいものを全て消す処理が必要になる。遅延セグ木でできるけど、さすがに他の方法があるだろうと考えるが、何も思いつかず結局遅延セグ木に。遅延セグ木は、和と「0にする」。このくらいなら実装も楽。

(追記)「ビルが見える」だとちょっと違うんだよね。対象のビルのてっぺんと目線を合わせたときに見えるかということだから、対象のビルによって目線の高さが違ってくる。コンテスト中にもちょっと考えてたけど、無限遠から見ているとすればよさそう。後ろから見てビルの間隔がどんどん広くなっていく感じ。このイメージを持つと、スタックでできるというのも納得しやすい。遅延セグ木を書いていたときも、まとめて消されるやつの個数が全体でO(N)ということに気づきたかった。あと、iに対する答えにビルiの高さが関係ないので、ソースコードがやや変わった見た目になる。

E - K-th Largest Connected Components

問題文を読むとUnionFind。k番目を得るのはstd::setではできないが、できる平衡二分探索木もあるらしい。こういうのはトライ木でできる。トライ木を貼って、とりあえず入力部分を書くために制約を確認していると、kが10以下。std::setでできるやつだったね。マージテクで解けるので、大きい10個だけ保存すればいいのは気づかなかった。

(追記)トライ木のマージテクで解き直した。ライブラリが整備してあれば実装は楽だが、std::setよりだいぶ実行時間がかかる。

F - Teleporting Takahashi 2

見た目行列累乗だったので、Mが小さいからそれを利用して(辺が張られた頂点だけ見て)行列累乗かと思い貼ってしまったが、よく考えると使えない。ちょうどL回動いて頂点iにいる移動方法が何通り、みたいのを考えると、やはりN通りばらばらに考える必要がありそう。隣の頂点へ移動することをベースに考えると、ずらしながらやればO(M)箇所しか更新が起こらず、in-placeでDPできれば解けそうだ。辺をソートすれば更新できるかと思ったが、両方の向きに辺があるので厄介。そこで、DPテーブルの他に、バックアップのテーブルと、(K回更新するうちの)今回更新されたかのフラグを世代で管理する。遷移元が今回更新されていないなら、更新する場所をバックアップして、普通に更新して、遷移先にバックアップしたというフラグを立てる(更新した位置に何回目の更新かを書き込む)。今回更新されていたなら、バックアップから更新して、遷移先にバックアップしたというフラグを立てる(ここはどちらも同じ)。これで、1回の更新がO(M)でできる。色々寄り道したのでだいぶ時間がかかってしまった。

(追記)解説のコードのように、M個の遷移(どこにどんな値を足す)をvectorに入れてから、それを使って実際の遷移をやれば、楽だし速い。