ABC337

F以外の6完。時間ギリギリで、結果オーライとしか。ペナルティなしは運もよかっただろう。体調に不安があり(結局大丈夫だったのかもしれないが)、時間がなくなってきて体が熱くなるのはいつものことだが、体力的にきついと感じた。なんか終了後も(ぷよぷよやF問題のネタバレがあるのに)Twitter見てしまうし。疲れている。楽に流れる。

ぷよぷよの配信を観ていて5分前に切り上げてABCへ。ぷよぷよ最強リーグやってたころは大変だったな。これからブログ書いたり気になるところを考えたりするのに、(できればTwitterを見る前に)配信の残りも観るんだから。

A - Scoreboard

チーム高橋が何点多いかを計算。出力が3通りってどうするのがいいんだろうね。今回はDrawを場合分けした。文字列は3つ全部コピペした。

B - Extended ABC

まあ定義はわかる。拡張の気持ちとしては、元の文字列を好きなだけ伸ばす感じ。ただし、長さ0に潰してもいい。耳DPを思った。そこで、状態をAからCまで動かす。文字列を前から見て同じ文字になるまで状態をインクリメント、DになったらNo、それ以外はYes。解説を見て気づいたが、与えられる文字列にABC以外の文字は含まれなかった。そうじゃなくてもis_sortedは使えるので思いつきたかった。

C - Lining Up 2

簡単そう250点だろとか言ってたらだいぶ混乱して時間かかった。グラフ構築してDFS(これは何も考えずにできるが実装時間がかかる)とか後ろから(具体的には何も見えてない)とかもちょっと思ったけど、さすがに正面からできると思ってそうした。まず先頭を探す。あとは後ろの人がわかればいいが、b[a[i]] = i; みたいにしておく(問題文の文章の語順から混乱した)。後ろの人が誰かという情報は末尾の人にはないが、N-1回だけ追うことでそこ以外を回れるはず。

今気づいたけど、与えられるAは前の人が誰かという情報なんだね。それを後ろという言葉で表現してるから俺は混乱した。こんな単純な設定でここまで難しくできるんだな。

D - Cheating Gomoku Narabe

緊張からトイレ(小)へ。全ての対象となる「K個のマス」について、各文字が何個あるかわかればいい。トイレで解けたが、やりたくないやつ。実装の見通しも立っているので、飛ばさずやる。まず、面積が20万以下なだけで形がわからないので、グローバル変数の2次元配列は使えず、サイズH*Wのvectorを使う(アクセス用のラムダ式を書く(インデックスを返すか参照を返すか迷ったが無難にインデックスとした(完成コードを見ると参照でよかったか)))。次に、横にKマス並んでいるのをやる。横幅がKマス以上あるときのみやる。最初のKマスをカウントしてから、終端が(半開区間で)KからWまでしゃくとりみたくやる(Wの位置ははみ出してるので更新せずに抜ける)。'x'が0個のとき、'.'の個数で最小値を更新する。ここまではいい。縦の場合も同じことを書かなければならない。これが「やりたくない」と思った理由。でもやるしかない。コピペして変更。慎重に変数名をハイライトして対称性があるか確認する。が、サンプル合わない。Kマス前のカウントを戻すとき、横にKマスのままにしていたのが原因。これは変数名ハイライトじゃわからない。実装とデバッグでけっこう時間を使ってしまった。

(追記)2回書かない実装をやった。マスの情報にラムダ式でアクセスするようにしてあるので、中身をいじればそのままできそう。HとWを入れ替えて、モードによってi,jの扱いを逆に。実装自体は短いけど、データを動かさずアクセスの仕方を変えるというのは普段やってなくて、HとWを入れ替えたこともあり、混乱してかなり(コンテスト中にやらなくてよかったと思うくらい)時間がかかった。ただまあこれはやるべき。同じことを2回書いてバグが存在する可能性のある場所を増やすのはありえない。

E - Bad Juice

M回やりとりするのかと思ってなかなか読解できなかった。M人の友達に同時に飲ませて一晩で判断するのね。最小というのが難しそうだけど、得られる情報量がMビットなので例えばN=8がM=3でできるか考えると、ジュースの番号(0-indexed)を2進法にして、友達i(0-indexed)には2^iの位が1のジュースを飲ませればよさそう。なんも考えてなくて、できるならこれだろうという感じで、合うように書くだけ。std::bit_widthとか使えたかもしれないけど1引いて符号無しにする手間かかるんだな(どっちにしろコンテスト中は調べる余裕なかった)。誰にも飲ませないジュースが存在するの、当たり前と言えばそうかもしれないが、面白い。

F - Usual Color Ball Problems

頑張って読んで、解ける気がしないという第一印象、ここで順位表を見る(いつもより早いタイミング)。Gのが解かれてるので、Gを読んだうえで希望がありそうなのでGへ。楽に流れているので、すぐに順位表を見てレーティング期待値最大化の動きをする。

終了後に少し考えたが、まあrotateじゃなくて2回繰り返した列の区間で考えるとして、それにしても最初の箱に何が入るか変わったりして不可能に思える。

(追記)これは難しい。何色のボールが何個あるかは前計算できる。何色の箱が何個ってわかれば、各色のボールが何個入るかわかり、答えもわかる。これに気づくのが難しい。これを使ってしゃくとりできるとわかるなら思いつけるかもしれないが、これを聞いてもしゃくとりできると思えない。まあ実際にはしゃくとりできる。開始地点を決めて、「全ての箱に1個以上のボールが入る」または「N個全てのボールが箱に入る」最短の区間を持つ。先頭を取り除くのが難しそうだが、ある色のボールが使う箱の数はそのボールの個数をKで割って切り上げればわかる。都合よすぎに見えるけど、箱が埋まるギリギリの区間を考えているということは、ボールを追加するときに必ず空き箱がある(箱に入らないボールがない)状況なのだ。全体の各色の個数と区間の各色の個数を持っておけばみんな差分計算できる。

G - Tree Inversion

u,w,vと並んでいて、wとvは同じでもいいがu,wとvは同じじゃダメ。とか頑張って考えていると、サンプルの説明に転倒数とズバリ書いてある。全方位木DPが見えるが、全方位木DPが必要ないやつかもしれない。結局色々考えた感触で全方位木DPにした。全方位木DPを使うたびにまず復習が始まる。各頂点を根とした根付き木に対する問題の答えを求めたい。まず、頂点1からDFSして下向き(親から子へ)の辺(辺の指す先の頂点を根とした部分木)に対する答えを求める。それが済んだらまたDFSして各頂点から出る全ての辺に対する答えを得て(親への辺の答えは呼び出し元の親から与える)、左右から累積和して、まず全部の和に現在の頂点を根として付けることでその頂点に対する答えがわかり、各辺についてその辺だけを除いた和を求めて現在の頂点を根として付ければ子に与える情報(子からの辺の答え)が得られてDFSに潜れる。さて、転倒数を求めるには、根を付けるときに自分の子孫で自分より小さい番号のものの数がわかればいい。木全体で自分より番号が小さいものの数は自明だし、DFSの子孫ならBITでどうにかできそう。まあ最初はこんなにすっきりわかってないし、解けるかどうかもわからない状態であがいてるんだけど。BITは、頂点を訪れたとき+1するだけで戻るとき-1はせず、個数を求めるときは子達に潜る前との差を使う。全方位木DPは抽象化してライブラリにしたつもりだったけど、全然わかってなかったなと使うたびに思ってる。特に今回は、BITを使うところ、根を付けるところに特殊な処理が必要で、直接DFS内に処理を書いていった(どう捉えればいいんだこれ)。終了7分前くらいに書き上がったけど、サンプルが合わなくていくつかミスを見つけて、サンプル通って提出してAC。残り1分を切っていた。

(追記)通常の全方位木DPより簡単な(よい性質を持っている)面もありそうなので、解説(難しくてざっとしか読めてないけど)のような方針でも書いてみた。wとしての寄与を頂点毎に求めて対象全体に足す(書くまで気づかなかったけど木上のいもす法ってこれのことか)。各子に対して自分(親)より小さい番号が何個あったか求め、その分の寄与を子の部分木以外に足す(全体に足して子の部分木から同じだけ引く)。子を全部調べたら、自分より小さいのが子孫以外に何個あるかもわかるので、それを自分の部分木に足す。最後に、根に全体に足す分を足して、根から各頂点までの和が答え。考えがなかなか整理されず、かなり時間がかかった。子孫とそれ以外に分けてサンプル通らないとかもやってた(布団の中で頭の中で考えたら間違いに気づいた)(頂点じゃなくて辺でなら木を2つに分断できる)。