2020年9月14日月曜日

CodeChef September Challenge 2020 - Chefina and Swap

0-basedで考える。列(Si)(S_i)[0,m),[m,N)[0, m), [m, N)に分けた時に、左右の和を等しくするようなスワップが何個あるか数える。まず、右の和のほうが大きいことが有効なスワップが存在する必要条件である。右の和が左の和よりD(m)D(m)だけ大きいとする。このとき、D(m)D(m)が偶数なら左の区間のSiS_iと右の区間のSi+D(m)/2S_{i+D(m)/2}を交換すると和が等しくなる。添え字iiが有効である条件は0i<m,mi+D(m)/2<N0\le i < m, m \le i+ D(m)/2 < Nで、これはmax(0,mD(m)/2)i<min(m,ND(m)/2)\max(0, m-D(m)/2) \le i < \min(m, N-D(m)/2)とまとまる。この区間の長さが有効なスワップの数に等しいので、すべてのmmについて足せばよい。ただしD(m)=0D(m)=0の場合は区間をまたいで数を移動する必要がないので特別扱いして、左の区間内と右の区間内のスワップの総数を数える。

さて、0D(m)<2N0\le D(m) < 2Nが必要なことを考えると、有効なmmは多くないはずだ。mmが増加するとき、D(m)D(m)が正から負に変わる地点を考えると、この地点は全体の半分よりは右にあるはずだから、その付近ではmm11動くごとにおおむね2N/2=N2 \cdot N/2 = N程度はD(m)D(m)が変化することになる。したがって、高々3つ程度のmmについて数えれば十分という見積もりができる。実装の際はD(m)0D(m) \ge 0であるような最大のmmを二分探索などで求めておいて、そこから下っていけばよい。

CodeChef September Challenge 2020 - Find XOR

仮にK=0K=0を尋ねることが可能だとすると、K=0K=0による総和とK=2eK=2^eによる総和の差は(Ai)(A_i)の中で第eeビットが11であるものの総数-第eeビットが00であるものの総数に等しい。これを利用すると各桁の立っているビットの数がわかるので、偶数ならXORを0、奇数なら1とすればよい。

実際にはK1K\ge1という制約があるが、代わりに全ビットを反転したもの(=22012^{20}-1とXORを取ったもの)をKKとすれば同じことができる。

また、素朴にやるとK=0,20,21,...,219K=0, 2^0, 2^1, ..., 2^{19}を全部尋ねる必要があってクエリの回数が21回になってしまう。K=20,...,218K=2^0, ..., 2^{18}を調べてK=0K=0の場合の生の総和と比較することで、K=219K=2^{19}は省略できて20回になる。


N103<210N \le 10^3 < 2^{10}だから、11ビットくらいで区切れば2つの質問を1つのクエリでできそうと思ったが、分離がかなり複雑になりそうなので方針を変えた。

CodeChef September Challenge 2020 - Rotate the Polyline

vi=PiPi+1v_i=P_iP_{i+1}とした時、原点を通る直線llによってできた2つの(境界を含まない)半平面のそれぞれに連続した添え字のベクトルが並んでいれば、llに直交するベクトルが答えということになる。ただし、答えとなるベクトルは十分に原点に近い格子点を通る必要がある。

整数性の条件をいったん忘れると、直線llを表すベクトルの候補uuは各viv_iから偏角を左右に微小にずらした2N2N通りが考えられる。そこで偏角順の尺取り法をして、それぞれのuuから偏角が180°180 \degree以内におさまるすべてのviv_iの添え字を保持することにする。添え字がすべて連続している瞬間があればuu90°90 \degree回転したものを答えにすればよい。管理と判定はBITでできる。以下、実装のポイント:

  • 尺取り法で区間を伸ばす場合の処理: ベクトルの候補uuviv_iのクロス積がマイナスになるまで伸ばしていけばよい。[1]
  • 尺取り法で区間を縮める場合の処理: uuviv_iのクロス積がプラスになるまで縮めればよい。
  • uuと同一か180度回転した偏角が(vi)(v_i)に含まれる場合、uu(を90°90\degree回転したもの)は答えにできない。

さて、2N2N本の直線を用意する上で、整数性の条件を考慮しなくてはならない。単純化のために正数に限定して考える。ベクトル(x,y)(x, y)から反時計回りに微小に偏角を進めたベクトルを得ることは、y/x<1y/x < 1であればファレイ数列F2109F_{2\cdot 10^9}においてy/xy/xの直後にある分数を得ることと同じである。y/x1y/x \ge 1の場合も、逆数を取って直前の分数を得ると考えればよい。やり方を知らないのでググったらstackexchangeの質問が見つかった。ファレイ数列の隣接項p/q,c/dp/q, c/dcqdp=1cq-dp=1を満たすので、c,dc, dは互除法で求まるようだ。


  1. クロス積がマイナスになるベクトルが存在しなかった場合はこれだとうまく処理できないように見える。こういうことが起こるのは、偏角ソートされた(vi)(v_i)の隣接項同士が180°180\degreeより開いている場合だが、問題設定より全ベクトルを足すと00に戻るのでこの範囲に1つもベクトルがないということはありえない。 ↩︎

CodeChef September Challenge 2020 - Divide Candies

べき乗和はKKの値に関わらず奇数と偶数を交互に足すので、総和は奇、奇、偶、偶、奇、奇、……となる。総和が奇/偶の時の自明な下限がそれぞれ1/0なので、これを達成できれば最善となる。実験してみると、NNが十分に大きければ常に下限が達成できるという予想が立つ。

K=1K=1の場合、長さ4の区間は01100110とわければよくて、NN44の倍数ならこれに従って等分できる。倍数でない場合もそれぞれ4つずつ等分した後、残りを適当にわければよい。

K=2K=2について。また実験すると、長さ8の区間は0110100101101001で等分できることがわかるので、NNが小さい場合の構成を列挙した上で、以降は88個ずつ等分すればよい。

K=3,4K=3, 4の場合も常に等分できるような長さの区間が見つかれば解決しそうだ。

K=3K=3の場合は長さ16の区間は01101001100101100110100110010110で等分できる。(ここでようやく、ひとつ小さい次数で得た列をflipしてくっつければよいことに気づく。)やはりNNが小さい場合を列挙して、以降は1616個ずつ等分すればよい。K=4K=4も同様で、長さ32の区間を0110100110010110100101100110100101101001100101101001011001101001で等分できる。

さて、NNが小さい場合の解を用意する必要があるのだが、明らかにIPソルバで解けるのでOR-ToolsのCP-SATを使った。K=3K=3までは愚直でいいとして、K=4K=4はDPや半分全列挙でも簡単ではないように見える。(ちゃんと考えていない)