ツバサの備忘録

主に備忘録代わりに精進記録を載せていくつもりです。

AGC036 B - Do Not Duplicate

問題
提出コード

解法

同じ状況になる部分をどんどん取り除き、最終的に残る数回を愚直にシミュレーションします。
まずは、空の数列sに対してi番目の数字A_{i}sに入れたときに、A_{i}を取り除くことで再び数列が空になるタイミングはどこか、を調べます。
これは、A_{i}の種類ごとにiを昇順にまとめ、iに対してはA_{i} = A_{j}となるような、i \lt jとなるjのうち最小の物をメモすればよいです(もし存在しなければ、A_{i} = A_{p}となる最小のpをメモします。i = pとなることもあります)。
次に、初期状態(sが空の状態で、次に挿入するものがA_{0}であるタイミング)まで一回ループするのに何回操作をするか、を調べます。
これは、次に操作する数列の番号iを最初にi = 0とし、先ほどのメモを見ながら、数列が空になるタイミングごとに区切って調べていけばよいです。
iに対してのメモがjの場合、j - i + 1回操作を追加すると数列が空になり、次に操作する対象はj+1番目になります。ので、操作回数にj - i + 1を加算し、i = j + 1と再定義します。ただし、i \geqq jの際は、j - i + 1 + Nとなります。
これを、再びi = 0となるまで行います。
メモの種類はもちろん添え字iの分だけなので、これは最悪でもN回の操作となります。
sが空かつ、i = 0で操作を開始して、再び同じ状態に戻ってくるまでの回数をLとします。すると、操作をする回数NKは、最終的に NK \ mod \ Lとなります。これを、新たにKと定義しなおします。
この時点でのKは、最大でN^{2}程度です(A_{i}が全て異なるパターンです)。
ここから、さらに値を減らします。
先ほど、1回ループするまでの回数をシミュレーションをしましたが、これと同じ操作をしつつ、今度はj - i + 1Kから直接減らしていきます。そして、j - i + 1を減らすとKが負になる、というタイミングでやめます。このシミュレーションは、先ほどと同じく最悪でもN回の操作で済みます。
こうすると、残っている操作回数Kは、最大でもNとなります(次に操作する添え字を問わず、数列が空の状態から再び空になる際の最悪ケースは、先ほどと同じでA_{i}が全て異なるパターンだからです)。
ということで、操作回数をNまで減らすことができたので、あとは現状のiから愚直にシミュレーションをしていけばよいです。
これは、stackを使うと、最大で2N回程度のプッシュ、ポップの操作になるので十分間に合います。すでにスタック内に存在するかどうかは、setを利用しました(こっちの方が計算量的には重くなると思います)。
計算量的には、おそらくO(N log N)です(自分は最初のA_{N}ごとにまとめる操作を行う際にc++のmapを使用したため)。

感想

ループをするのでその部分をどんどん減らす、という発想が割とはやいタイミングでできたので良かった…です。