ARC136

ADの2完。BがWAで、少し考えて一旦捨てた(昨日のことがあるのでどうしてもそういう判断になる)。結果的にはDまでの4問を楽しめた。不満を挙げるとするなら、Cを考える時間があまりとれなかったことか。

A - A ↔ BB

不変量がありそう。ABは元々全部Bで、2つ連続したBBにでかいAを被せていると捉えることができる。つまり、元々のAが存在しない文字列に変換して、Bが連続する各区間についてAを被せるかを考えればいい。区間の先頭はAにできるならしたほうがいい、その次からもそう。よって先頭から貪欲にAにしていく。簡単だけどかすかに天才感があって好き。

B - Triple Shift

偶置換とかそういうのだと思ったけど、単に転倒数を比較するだけでは同じ数が複数ある場合に正しい答えが出ないと思った。ここは本当に自分の数学力のなさ、競プロに関することでさえ全く追究する気持ちがなかったということを見せつけられた。パリティが関係しそうだとは思ったので、具体的に辞書順最小へ正規化してAとBが一致するか見ればいいと思った。左から1文字ずつ持ってくる。最後の2文字を残して(昇順に)ソートできたとき、5 6 5みたいのが残ったらもう1回操作して5 5 6にしないといけない。提出してWA。5分くらい考えてCへ。

今気づいたけど、そもそも同じ数が複数含まれるなら、その同じ数同士をスワップすることで偶奇を自由に行き来できる(5 5と5 5は区別できない)(5 6 5の最初を2つをスワップするのと同等の操作が可能)。自分の誤解法も、その同じ数を使ったスワップをすると得するケースでしてなかった点で間違っていたのだ。これすごく面白くて、わからなかった点を考えなくていい解法にシフトしたつもりが、気づかないミスが含まれていてそれがまさにわからなかった点だった。いい問題だし、何にも興味を持たずつまらない生活を送っている自分が悲しい。

C - Circular Addition

かなり難しい。まずmin(A)で全体にかけてしまっていいかと思ったけど、図を描いているうちに1 3 2 3 1という反例を見つけた。1回の操作で数の種類数を1個しか増やせないわけでもない。なんか簡単な解法が存在してそうにも思うが、何も見えなかった。

D - Without Carry

簡単そうに見える。繰り上がりがないもの、ということは繰り上がりによって繰り上がりが発生するようなケースも考える必要がない。とりあえず各桁の数字の個数をカウントしながらやっていく処理を書いてみると、6個の桁のうち「少なくとも1つ」繰り上がりが発生するものをカウントしなければならないのだった(今にして思うとこれ気づかないのヤバいな)。ということで包除を考える。自分自身も含む順序付きペアで考えて、自分自身とで繰り上がるかは愚直に求めて引き、2で割れば答え。さて、2^6=64で64倍の計算量になるとそこへ更に10倍とか64倍とか掛かったときにいくら4秒といっても心配になる。64通りの高速ゼータ変換をしたい。そのままはさすがに無理なので、bitDPみたいにして差分計算していくのを思いついた。こういう展開のとき定数倍を甘く考えていいという意味でC++はかなり有利だと思った(すっきり解けていれば別だろうけど)。

しばらく実装していくとそもそも広さ100万の高速ゼータ変換では一部の桁だけに注目するとき該当する部分の総和がO(1)で求まらないのでこの方針では無理だと気づいた。同時に、単に高速ゼータ変換すればその総和が求まっていることにも気づいた。後回しにしていた高速ゼータ変換の実装に入る。できるはずだと思ってやってきたが、2ではなく10(あるいは11)でやる具体的な実装が全く見えていない状態でここまで進めてきた(実力ギリギリの問題が相手だと立ち回り的にこういう展開を避けられないことは多い)。ググって出てくるいつものを見て、6203みたいなある桁だけ0の数を全部生成してそこから6293までの範囲で累積和をやるとイメージできた。6重ループを書くとか6種類の処理を書くとかは考えにない。0固定にする位置6通りをforで回して1次元配列でやればいい。ここで、6次元超立方体のサイズを10にするか11にするか迷っていたが、11にせざるを得ないと判断した(総和と0(片方が0で繰り上がるケースの通り数)の両方を返す必要がある)(11にしても計算量が2倍にはならないこと、2倍になっても大丈夫なことは確認済み)。下から桁を得て上から入れるミスにも気づき、書き上がる。サンプルが合わない。いつの間にか繰り上がるほうを求めていた。色々いじったが、素直に繰り上がるほうを求めてN*(N-1)/2から引くことにしてサンプルが通り提出してAC。

終了後、包除原理は要らないという情報を見て考え直すと確かに要らない。「少なくとも1つ」というのから安易に包除へ行ってしまった。単に全て繰り上がらないという話でよかった(何が起こっていたのかまだよくわかっていない)。こちらの方針だと、サイズが10でいい(0は繰り上がる相手がいないが、繰り上がらない相手は誰にでもいる)。そうなるとA_iの値がそのまま使えて実装が楽になるし、自分自身を足して繰り上がらないものの個数もc[444444]に入っている(!)。10分もあれば書けそうな短いコードになってしまった。面倒だと思った処理は一つも必要でなかった。