ARC122

3完。maroon回ということで、ぷよぷよ最強リーグは見ずに臨んだ。結果的にはあまり楽しめなかった。順番にDまで見ていったかぎりでは解けそうな問題ばかりなので、2問を並列に考える状態にならず、細かいところを詰める時間が長かった。素早くシンプルに書ける実力がないのがいけないんだけど、それでレーティングが下がるのはいいとして、楽しめないのはひどいじゃないか。

A - Many Formulae

通り数はフィボナッチ数になる。まずフィボナッチ数列を求めておく。先頭だけは毎回プラスにカウントされる。最初はなんもわかんなくて、手を動かして分岐を描いていた。冷静になると、各演算子についてプラスのときとマイナスのときの通り数は簡単に求まる。結局最後まで符号が前の数と今の数の間にあるという思考のままだったけど、今になってみれば符号は単に今の数に付いているとわかる。ずっと混乱していた。さて、プラスのときは簡単で、マイナスのときは両隣がプラス固定になる(これも最初はわからなかった)。これでも解けるが、両方合わせて何通りになるかはフィボナッチで計算済みなので、そこからプラスのときの通り数を引けばいい(混乱していたので求めるものが少ないこちらの実装を選択した)。

B - Insurance

なんとなく三分探索できそうだが、整数じゃないからやりたくない。まあ三分探索やるにしても正当性の確認は必要だし探検してみる。各シナリオについて、「失う金額」のグラフを描くと、abs(x)を右下にずらしたみたいな単純な形をしている。N個の和が最小になるのがどこか考えると、最小値をとる場所からの距離がコストだから単に中央値だ。ソートしてa[n/2]を使うのが簡単(Nが偶数のときは最小値をとるxが区間になるので区間の両端どっちでもいい)。ここでも各項を別々に求めようとして混乱したりしてたが、xが決まったんだからあとは愚直に計算すればよかった。保険を最高に効率よく使ってちょうど半分というのが感覚になかった。

C - Calculator

10^18は2^60より少し小さい程度なので、130回という操作回数はまあそんなもんかとまず思う。最速で増やす(操作1のあと操作4と操作3を繰り返す)のを実験して、これもフィボナッチ。86とか必要なので、倍率が2より小さいとはいえ途中で1足すをあまりテキトーにやると通らないだろうなとここで気づく。途中で1足したときの寄与を実験する。これも、言われてみれば当然のフィボナッチ。大きいほうに足す(大きい数を作りたいので(そこで終わりでない場合は一緒なの気づいてたか?))。最初の操作1(または操作2)も同じものだった。次に、「フィボナッチ数列のいくつかの和」でググり「ゼッケンドルフの定理」に辿り着く。連続して選ばない方法を貪欲で構成できるらしい。細かいところはわからないが、1.5倍ならおおむね130回に収まりそうだ。何番目を使うか調べて、その配列をうしろから見て1を足すか反対側に足し込むか決めていく。あとはデバッグ用に実際に構成しながら操作番号を出力していく。10^17はできるのに10^18でうまくいかず、けっこう悩んだあげく減少for文の使い方が間違っていた。提出してWAで、大きいほうに足す処理で操作番号がずれていた。やはり時間をかけてでもきれいに書くのがいいな。操作列の構成で頭壊れるので、かなり時間を使ってしまった。1足して次のターンではまだ1しか寄与しないというのも混乱ポイント。

D - XOR Game

残り30分を切っているので雑に考える。まず1bitのケースを考えると、1が偶数個かで決まる。2bitでも同じ数が偶数個ずつあればBobは同じ数で合わせていけばいい。(それが最善であることの正当性はわからないが)Bobは予めN個のペアを用意しておき、常にペアで消すようにすることができる。ということでペアを作る問題になった。立っている最上位ビットを見て、それが偶数個ならそのビットが同じ同士でペアにするしかないので、1bit小さい問題2つに分割できた。奇数個のときは、立ってるのと立ってないのをペアにする必要があり、xorが最小になるペアを見つければそれでいい。より下のビットだけが必要な情報で、すぐ下のビットで同じものがあればその中のどれかをペアにする、なければ(片方が全部1でもう片方が全部0みたいな)やはりペアにするしかないが、いずれにせよ一つ小さい(さっきのとは別の)問題に帰着できそう。ただ、今考えてもかなりきつそうな方針。

(追記)解説の「明らかに矛盾するため」のところがわからなかったが、ゲーム結果の中で最もスコアが小さいものを持ってきてBobはそのペアに従ってプレイすればそのスコアを達成できる。あとは再帰しながらトライ木を使う。奇数個+奇数個に分かれたら、片方をトライ木に入れて、もう片方のそれぞれについてできるだけ差が小さい相手を探しその最小値を求める。それらの最大値が答え。トライ木で相手を探すとき、そのビット位置で同じのがなければ違うのにするんだけど違うのは1通りしかないので一意に定まる(当たり前なんだけどここがよくわかってなくて不安だった)。