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