Official

F - Make Adjacent Editorial by chinerist


各整数 \(k=1,2,\dots,N\) に対し、 \(A_i=k\) が成り立つ \(2\) つの \(i\) のうち、小さいほうを \(L_k\) 、大きいほうを \(R_k\) とします。

[1] 良い数列にするのに必要な操作回数

まず \(A\)良い数列 にするのに必要な操作回数について考えます。

これは最終的な 良い数列 を決め打つと転倒数と同じように計算することができますが、とくに \(2\) 種類の整数の位置関係により操作回数への寄与を考えると、以下の \(3\) パターンに分類できます。

  • \((\dots,1,\dots,1,\dots,2,\dots,2,\dots) \rightarrow (\dots,1,1,\dots,2,2,\dots) \) or \((\dots,2,2,\dots,1,1,\dots) \)

  • \((\dots,1,\dots,2,\dots,1,\dots,2,\dots) \rightarrow (\dots,1,1,\dots,2,2,\dots) \) or \((\dots,2,2,\dots,1,1,\dots) \)

  • \((\dots,1,\dots,2,\dots,2,\dots,1,\dots) \rightarrow (\dots,1,1,\dots,2,2,\dots) \) or \((\dots,2,2,\dots,1,1,\dots) \)

それぞれについて寄与を計算すると、基本的には \(L_i\) が小さい \(i\) を左側にしたほうがいいことがわかります。よって \(A\) について \(L_i\) の昇順に並び替えることで 良い数列 にするのに必要な最小の操作回数を達成できます。

[2] 辞書式順序の最小化

[1]の考察を踏まえて辞書式順序の最小化を考えます。\(A\) における位置関係について \(1,2\) 番目のパターンについては並べ方は一意に決まりますが、 \(3\) 番目のパターンについてはどちらでもいいです。そうすると一連の操作後の良い数列\(1\) 項目から順に決めていく際、まだ登場していない整数 \(k\) のうち次の項に配置できるのは、\(L_{k'} < L_k\) を満たす \(k'\) すべてに対して \(R_{k} < R_{k'}\) が成り立つ \(k\) となります。

これをもとに答えを求める手順を整理します。\(1\) から \(N\) までの整数からなる順列 \(P\)\(L_{P_i} < L_{P_{i+1}}\) が成り立つ順列とします。一連の操作後の良い数列\(1\) 項目から順に決めていく際、以下のようにすれば辞書式順序最小の 良い数列 \(ans\) が求まります。

  • 整数列 \(X\)\(X=(P_1,P_2,\dots,P_N)\) で初期化する。また、数列 \(ans\)\(ans=()\) で初期化する。
  • 以下の操作を \(N\) 回繰り返す
    • \(\displaystyle \min_{1 \leq i \leq k} R_{X_i} = R_{X_k}\) を満たす \(k\) に対する \(X_k\) のうち、もっとも小さい値 \(x\)\(ans\)\(2\) 回追加する。その後、\(x\)\(X\) から取り除く

これを愚直に実装すると最悪 \(\Theta (N^2)\) かかります。

\(\displaystyle \min_{1 \leq i \leq k} R_{X_i} = R_{X_k}\) が成り立つ \(X_k\) を「低い項」と呼ぶことにすると、一度「低い項」になった \(X_k\) は以降常に「低い項」です。よって \(X\) から項が取り除かれた際にあらたに生じた「低い項」を高速に検出することができれば、「低い項」を優先度付きキューで管理することで上記の手順を高速に行うことができます。

[3] 「低い項」を検出するセグメント木

上記で述べたような項の削除と「低い項」の検出は

  • 値1:区間内の \(R_{X_k}\) の最小値
  • 値2:数列を区間内に限定した場合「低い項」になる項であって、数列全体では「低い項」になっていない \(X_k\) に対する \(R_{X_k}\) の最小値(存在しない場合 \(\infty\)

を管理するセグメント木によって高速にできます。

(注:値2について「数列全体で『低い項』になっていない」と表記していますが、正確には「数列全体で検出済みの『低い項』になっていない」です)

まず項の削除については \(R_{X_k}\)\(\infty\) に更新することで代用できます(つまり、\(k\) に対応する葉ノードの値1を \(\infty\) に更新する)。そしてあらたな「低い項」の検出については、数列全体を管理するノードがもつ値2が \(\infty\) でなければ、削除によって新たな「低い項」が生じたとわかります。値2では数列全体で「低い項」になっている \(X_k\) を除いて考えているため、あらたな「低い項」を検出した際にもセグメント木を更新する必要がありますが、これは \(k\) に対応する葉ノードの値2を \(\infty\) に更新すると考えればいいです。

実装についての詳細はこちらなど参照してください

項の削除や「低い項」に伴うセグメント木の更新は \(O(N)\) 回であるため、全体で \(O(N\log N)\) で答えを求めることができます。

posted:
last update: