Official

G - Suitable Edit for LIS Editorial by Cyanmond


前提知識: LIS (最長増加部分列) の高速な計算 (ABC354-F の解説より)

与えられた列 $A$ に対して、  LIS (最長増加部分列) は以下のようにして求められます。

$A$ を座標圧縮して $1 \leq A_i \leq N$ を仮定します。動的計画法を用います。$\mathrm{dp}[i][j]$ を $A$ の $i$ 番目までの要素を考えて最後に用いた要素の値が $j$ である LIS の長さと定義します。初期状態は $\mathrm{dp}[0][0] = 0, \mathrm{dp}[0][1] = \mathrm{dp}[0][2] = \dots = \mathrm{dp}[0][N] = \mathrm{-inf} $ とし、遷移は、 $i = 1, 2, \dots ,N$ について順に、

  • $j = A_i$ について $\displaystyle \mathrm{dp}[i][j] = \max_{0 \leq k < j} \mathrm{dp}[i - 1][k] + 1$
  • $j \neq A_i$ について $\displaystyle \mathrm{dp}[i][j] = \mathrm{dp}[i - 1][j]$
とします。$\displaystyle \max_{0 \leq k \leq N} \mathrm{dp}[N][k]$ が LIS の長さです。列自体を復元することもできます。

愚直に実装すると計算量 $O(N^2)$ ですが、 $O(N \log N)$ にできます。大きく二つの方法があると思いますが、 Segment Tree を用いる方法を紹介します。

先程の DP を疑似コードにすると、非常に単純になります:

dp = [N + 1] * [N + 1]
dp.fill(-inf)
dp[0][0] = 0
for (int i : (1, N))
    max_value = 0
    for (int j : (0, A[i] - 1))
        max_value = max(max_value, dp[i - 1][j])
    dp[i][j] = max_value + 1

DP を in-place 化すると、区間の最大値を求められる Segment Tree を用いてこれは以下のように書けます。

RangeMaxSegmentTree dp(N + 1, -inf)
dp[0] = 0
for (int i : (1, N))
    dp[a[i]] = dp.calc_max(0, A[i] - 1) + 1

Segment Tree での区間 max は $O(\log N)$ の計算量なので、 LIS の長さが $O(N \log N)$ で求められることになります。補助的な配列を用いれば LIS を復元することもできます。

\(A\) の LIS の長さを \(L\) とします。まず、答えが \(L\) もしくは \(L + 1\) であることを確認します。 例えば \(x=1, y=A_1\) として操作すれば操作後の LIS の長さ \(L\) を実現できます。また、 \(1\) 要素のみ編集して LIS の長さが \(2\) 大きくなることはありません。よって、答えは \(L, L + 1\) のどちらかです。

解法 1

操作で \(A_i\) を編集するとき、 \(i=1\) ならば \(0\)\(i \neq 1\) ならば \(A_{i - 1} + 1\) に編集するとしても答えは変わりません。

証明の概要

答えが $L + 1$ になるとします。編集した後の $A$ の長さ $L + 1$ の狭義単調増加部分列は、編集する前の $A$ の LIS と編集した要素からなる必要があります。 逆に、 $A$ の全ての LIS について、その LIS と編集した要素からなる長さ $L + 1$ の狭義単調増加部分列が作れるかどうかが上の制限に影響されないことを示せばよいです。

長さ $L$ の LIS の "すき間" の要素をうまく編集して、長さ $L + 1$ の狭義単調増加部分列を作ることを考えます。このとき操作に上の制限を設けても問題ないことは、 (厳密に証明すると長くなりますが) 簡単にわかります。

上の事実を用いて、定数個の状態をキーの \(1\) つとして持つ DP 、いわゆる “耳 DP” で解きます。

\(\mathrm{dp}[0][i][j] := \) 要素 \(i\) まで考えて値 \(j\) を最後に用い、まだ操作をしていないときの狭義単調増加部分列の長さの最大値
\(\mathrm{dp}[1][i][j] :=\) 要素 \(i\) まで考えて値 \(j\) を最後に用い、もう操作をしたときの狭義単調増加部分列の長さの最大値

などと DP を定義できます。(LIS の長さを求める DP と同様に) この DP を in-place に実装すると、計算量 \(\mathcal{O}(N \log N)\) でこの問題を解けます。

解法 2

\(i\) について、 \(A_i\) を編集して長さ \(L + 1\) の狭義単調増加部分列を作れるかどうかを判定します。

自然な判定方法として、以下の方法があります。

  • 全ての整数 \( x \ (0 \leq x \leq L)\) について、 \(A[1 : i - 1]\) から長さ \(x\) の狭義担当増加部分列を取り出したときの最後の要素として考えられる最小値、 \(A[i + 1 : N]\) から長さ \(x\) の狭義単調増加部分列増加を取り出したときの最初の要素として考えられる最大値を求め、 \(a, b\) とする。\(a + 1 < b\) かどうか判定する。

これを素直に実装すると計算量が \(\Theta(N^2)\) になります。

LIS の長さを求める (in-place な) DP に注目します。\(i\) から \(i + 1\) への遷移において、 DP 配列のうち値が変わるのは \(1\) 箇所のみです。

\(i\) について、 \(i\) から \(i + 1\) への遷移において DP 配列のどこの値が変わったかメモしておきます。後ろからも同様に DP をします。メモした情報を活用すると、計算量 \(\mathcal{O}(N \log N)\) で解くことができます。

posted:
last update: