ARMERIA

Rubyと競技プログラミングの話 AtCoderやCodeforcesの問題解説記事が多め。

AGC031 C - Differ by 1 Bit

C - Differ by 1 Bit

解法

コンテスト本番で私が考察・実装した方針を書いていきます。公式解説とは実装の方針が異なっていて、公式解説のほうが恐らく楽な実装だとは思いますが、ACを得るまでの1つの過程として参考になればと思います。

YES/NO判定

まずはここから。数列が1つ進むごとにビットが立っている個数が  +1 または  -1 されること、数列の長さが偶数であることから、最初の要素と最後の要素でビットが立っている個数の偶奇は必ず異なっているはずです。なのでもし  A, B でビットが立っている個数の偶奇が一致していれば、問答無用で NO を出力すればよいです。

では偶奇が異なっている場合には必ず問題の条件を満たす数列が存在するでしょうか。なんとなく存在しそうな気がします(本番ではここで  N=3 のときの  8! 全列挙コードを書きました)。それはこれから具体的に構成していくことで示していきます。

考えやすい形への帰着

以降、「数列の隣り合う要素は2進表記でちょうど1桁だけ異なる」ことを単に「条件を満たす」と表記します。

まず、スタートもゴールも変数だとややこしいのでスタートを  0 にします。条件を満たす数列  A, ..., B は全ての要素に  xor A を掛けてもやっぱり条件を満たします。そのため  X = A xor B として、条件を満たす数列  0, ..., X を構築すれば、その全ての要素に  xor A を掛けて復元したものが答えになります。これでスタートを  0 に固定できます。

さらに  X のビットが散らばっていると考えにくいので、ビットの位置を並び替えて上位ビットに  1 を固めます。たとえば2進数で  X = 00100101 に対して、ビット列を並び替えて  X = 11100000 とします。これも後で位置を戻して復元します。

これらの操作によってこの問題は、スタートは  0、ゴールは  11...100...0 (1の個数は奇数で、 X の1の個数と同じ)であり条件を満たす数列を構成する問題に帰着することが出来ます。

構築

構築のアイデアは以下の通りです。

f:id:betrue12:20190317161659p:plain

図のように「 0 で始まるもの全部」「 10 で始まるもの全部」…という風に列挙しながら、一番上の桁を1つずつ  1 にしていきます。上からビットを固めていくので、最後まで影響を受けない一番右側のビットを調整に使います。

このように構築すると、ゴールの  1 の個数が奇数であれば必ず  2^{N} 通りの全ての値を尽くしてゴールにたどり着くことができます。

この構築をする上で必要なのは、図中青破線で示しているところで使う「  2^{i} 通りの数を尽くし、隣り合う要素はちょうど1ビット異なっていて、 0 で始まり  1 で終わるような数列」もしくはその逆順です。

これは再帰的に作ることが出来て、長さ  2^{i-1} のときの数列が得られているとすると

  1. 長さ  2^{i-1} のときの数列を2倍したもの
  2. 長さ  2^{i-1} のときの数列を2倍して1足したものの逆順

を結合することで長さ  2^{i} のときの数列を得ることが出来ます。例えば  \lbrace 0, 2, 3, 1\rbrace から上記の方法で長さ8の数列を作ると  \lbrace 0, 4, 6, 2, 3, 7, 5, 1\rbrace となります。

仕上げ

以上の方法で、スタートは  0 、ゴールは  11...100...0 であり条件を満たす数列を構成することができました。

最後にビットの位置を戻して、全要素に  xor A を掛ければ元の問題に対する答えが求められます。

ACコード

Submission #4601254 - AtCoder Grand Contest 031