Official

H - Smooth Subsequence Editorial by cn449


\(dp_{i,j}\) を隣接する \(2\) 項の差の絶対値が \(D\) 以下かつ末尾が \(j\) であるような \((A_1, A_2, \dots, A_i)\) の部分列の長さの最大値とします。

このとき、

\( dp_{i,j} = \begin{cases} \displaystyle\max_{k \in [A_i-D, A_i + D]} dp_{i - 1, k} + 1 & (j = A_i) \\ dp_{i-1,j} & (j \neq A_i) \end{cases}\)

が成立します。この式に従って dp 配列を計算すると、正しい答えは得られますが時間計算量および空間計算量が \(\Theta(N^2)\) となってしまいます。

そこで、\(dp_{i - 1,j}\)\(dp_{i,j}\) の値がほとんどの \(j\) で一致することを用いて高速化を行います。

\(dp_{i,j}\) を 各 \((i,j)\) について陽に持つのではなく配列の使い回しをすることで、上の dp は \(1\) 次元配列 \(dp\) を持ち、\(i = 1, 2, \ldots, N\) の順で \(dp_j\)\(\displaystyle\max_{k \in [A_i-D, A_i + D]} dp_k + 1\) で置き換えるというように書き換えられます。

これで空間計算量は \(O(N)\) となりました。時間計算量は max を愚直に計算すると \(\Theta(N^2)\) のままですが、区間 max 一点更新の segtree を用いることで \(O(N \log N)\) となります。

実装例

#include <bits/stdc++.h>
using namespace std;

#include <atcoder/all>

int op(int a, int b) { return max(a, b); }
int e() { return 0; }

const int A = 500010;

int main() {
    int n, d;
    cin >> n >> d;
    vector<int> a(n);
    for (int &e : a) cin >> e;
    atcoder::segtree<int, op, e> seg(A);
    for (int e : a) {
        int l = max(0, e - d);
        int r = min(A - 1, e + d);
        int mx = seg.prod(l, r + 1);
        seg.set(e, mx + 1);
    }
    cout << seg.all_prod() << '\n';
}

posted:
last update: