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