1. 程式人生 > 其它 >cf1479 B2. Painting the Array II

cf1479 B2. Painting the Array II

題意:

定義一個數組的價值為它的段數:每段連續的相同數字只保留一個,其他刪除。最後剩下的陣列的長度就是段數。

把給定陣列分成兩個子序列(順序不變,每個元素一定屬於某個子序列),求兩個子序列的價值和的最小值。

\(1\le a_i\le n\)

思路:

我是官方題解復讀機。。。

法一:dp

如果把某數字 \(x\) 放入某組,那肯定要把它後面連續的 \(x\) 也放進這組。因此可以先處理一下原陣列,使其沒有相鄰的相同數。

\(f(i)\) 表示處理到 \(i\),且 \(a_i\)\(a_{i-1}\) 不在同一組的最小价值。

怎麼計算答案?假設 \(i\) 是最後一個 “\(a_i\)\(a_{i-1}\)

不在同一組” 的位置,那麼答案為 \(\min\limits_{1 \leq i \leq n} f(i)+n-i\)

怎麼dp?首先初值 \(f(0) = 0,f(1) = 1\)

假設 \(1\le j < i\)\(i\) 之前的最後一個 “\(a_j\)\(a_{j-1}\) 不在同一組” 的位置,也就是說 \(a_j,\cdots ,a_{i-1}\) 在 同一組,\(a_{j-1}\)\(a_i\) 在另一組。那麼 $ f(i) = \min\limits_{1 \leq j < i} { f(j) + (i-1-j) + [a_{j-1} \neq a_i] }$

上述轉移是 \(n^2\) 的。怎麼優化?

\(g(j)=f(j) + (i-1-j) + [a_{j-1} \neq a_i]\),則 \(f(i) = \max\limits_{1\leq j < i}\{g(j)\}\)。然後有兩個結論:

  1. \(a_i \neq a_{j-1}\) 時,只需考慮 \(g(i-1)\)

證明:$g(j) =f(j)+(i-1-j)+1 =f(j)-j+i $

\(f(i)\le g(j)\),所以 \(f(i)-i\le f(j)-j\),即 \(f(i)-i\) 單調不增

所以 \(g(j)=f(j)-j+i \ge f(i-1)-(i-1)+i =f(i-1)+1>f(i-1).\Box\)

  1. \(a_i=a_{j-1}\) 時,只需考慮最大的那個 \(j\)

證明:若 \(k<j,a_i=a_{j-1}=a_{k-1}\),那麼 \(g(k)=f(k)+i-1-k \ge f(j)-j+i-1=g(j).\Box\)

綜上,只有兩種情況。

int a[N], f[N], las[N]; //某值上次出現的位置
int g(int i, int j) {
    return f[j] + i - 1 - j + (a[j-1] != a[i]);
}
void sol() {
    int n, m = 0; cin >> n;
    for(int i = 1; i <= n; i++) {//讀入時去掉相鄰的重複值
        cin >> a[i]; if(a[m] != a[i]) a[++m] = a[i];
    }

    f[1] = 1;
    memset(las, -1, sizeof las); las[a[1]] = 1;
    int ans = m;

    for(int i = 2; i <= m; i++)
        f[i] = min(g(i,i-1), g(i,las[a[i]]+1)),
        las[a[i]] = i, ans = min(ans, f[i] + m - i);

    cout << ans;
}