AGC009-C:Division into two

きれいな問題だなあと思った。

解法

 簡単のためA\lt Bとする。逆のときはswapしても問題ない。こんなのdpしかないやろという見た目がするので,dpを考える。制約を無視すれば,今何番目の数を見てるかということと,XまたはYに最後に振り分けた数はどれだったかということが必要に見える。そこでdp_{i.j}を「i番目まで振り分け済みで,最後にYに振り分けた番号がjのときの振り分け方の総数」とできる。遷移は,①S_i-S_{i-1}\geq Aならdp_{i-1,j}からdp_{i,j}に遷移できる。また,S_0 = -\inftyとして,0\leq j \leq i-1について,②S_i-S_j\geq Bかつ,j+1からiの間の2項間の差がすべてA以上であるときに,dp_{i-1,j}からdp_{i,i}に遷移できる。(なぜj+1からかというと,S_j-S_{j+1}\lt Aでも,S_jYに振り分けているから,j+1Xに振り分ければ問題ないからである。)もちろんこれを愚直にやるとO(N^3)で,到底間に合わない。
 よく考える。まず,「jからiの間の2項間の差がすべてA以上である」かということをdpの更新のときに逐一調べる必要はない。つまり,S_i-S_{i-1}\lt Aであれば,S_{i-1}より手前の数は,これ以降②タイプの遷移が不可能だとわかるので,そういう番号を管理する変数を持っておけばよい。これをidとし,定義を「現時点で②のタイプの遷移が可能な番号の最小値」とする。さらに,S_i-S_j\geq Bをみたす最大のjを二分探索し,それをsとすると,dp_{i,j}に遷移してくるのはj2つ目の添え字がidからsまでの数である。だから,累積和を取っておけば②のタイプの遷移はO(\log N)で可能である。
 タイプ①の遷移だが,これは1つ目の添え字が1増えるだけである。タイプ②の遷移も,1つの目の添え字は1増えるだけだから,結局dp_iを「最後にYに振り分けた数の番号がiであるときの振り分け方の総数」として,これをiが小さい順に埋めれば十分である。最後に,明らかに答えが0になるパターンの存在に気を付けよう。それは,あるiが存在してS_i-S_{i-2}\lt Aとなることである。S_{i-1}XにもYにも振り分けることができなくなるからだ。
 以上より,次のようにすればよい。

  1. S_i-S_{i-2}\lt Aとなるiがあったら答えは0
  2. dp_iを「最後にYに振り分けた数の番号がiであるときの振り分け方の総数」とし,dp_0=1とする。また,idを「現時点で遷移してくることが可能な番号の最小値」とし,id=0とする。iが小さい順にこれを埋める。最初に,dp_i += dp_{i-1}とする。S_i-S_j\geq Bをみたす最大のjsとし,s\geq idであればdp_i += dp_s-dp_{id-1}とする(s\lt idのときは遷移できない)。最後に,S_i-S_{i-1}\lt Aであればid=i-1とする。
  3. 答えは,dp_N-dp_{id-1}である。

実装

Submission #4183566 - AtCoder Grand Contest 009
二分探索のとき,s=0のときの扱いに注意。あとid=0のときも注意。

ちゃんと考察すると綺麗な遷移でコードも短い,好き。