yamake's blog

主に競プロ、たまに自転車

Codeforces Round #650 F2-Flying Sort (Hard Version)

これを解説ACしました。

問題概要

長さn の配列a が与えられる。
配列内の要素を先頭に持っていく/最後尾に持っていく という操作を繰り返してソートしたい。
最低何回ソートをする必要があるか?

言い換え

ソート後の配列の連続部分列かつ a の部分列である配列の長さの最大値をn から引いたものを求める。

やること

座圧してDPをします。以降は座圧しているものとして考察を進めます。
dp[x][j]:=部分列の最後の要素である\ x\ の状態が\ j\ であるときの長さの最大値、または\ x\ の数
と定義します。dp[x][1] にのみ x の数をもちます。
また、cnt[x]:=\ a\ 内の\ x\ の数 とします。
x の状態 j について、全てを考慮すると cnt[x] の数に一致します。
その時の計算量は O(N^2) になってしまうので間に合いません。

実は条件を満たす部分列の性質を考えると x の状態は

  1. まだ1つめを見ていない
  2. 途中
  3. 全部見た後

の3つに抑えられます。
なぜなら条件を満たす部分列では x

  1. 最初に入る
  2. 最後に入る
  3. 途中に入る

の3種類しかなく、それぞれの場合で

  1. xcnt[x] 個以下使われる
  2. xcnt[x] 個以下使われる
  3. cnt[x] 個使われる

しかないためです。したがって、配列の先頭から順々に見ていき、DP配列を更新しながら上の3つをチェックしていくとできます。
DP遷移は提出コードを参考にしてください。
時間計算量は最初の座標圧縮により O(N \log(N)) です。