cf1479 B2. Painting the Array II

题意:

定义一个数组的价值为它的段数:每段连续的相同数字只保留一个,其他删除。最后剩下的数组的长度就是段数。

把给定数组分成两个子序列(顺序不变,每个元素一定属于某个子序列),求两个子序列的价值和的最小值。

\(1\le a_i\le n\)

思路:

法一:dp

\(f(i,x)\) 表示处理到 \(i\),某组的最后一个数是 \(a_i\),另一组的最后一个数是 \(x\)

\(a_i\) 接到 \(a_{i-1}\) 的后面:\(\forall x, f(i,x)=f(i-1,x)+[a_i\neq a_{i-1}]\)

\(a_i\) 接到 \(x\) 的后面:\(f(i,a_{i-1})=\min\limits_x\{f(i-1,x)+[a_i\neq x]\}\)

去掉一维,第一个变成 \(f(x)+=[a_i\neq a_{i-1}]\) 。就是个全局加

第二个变成 \(f(a_{i-1})=\min\limits_x\{f(x)+[a_i\neq x]\}=\min\{f(a_i),1+\min\limits_{x\neq a_i}f(x)\}=\min\{f(a_i),1+\min\limits_{x}f(x)\}\) 。那只需维护一个全局最小值

注意注意:(个人理解)显然上面两种方式需要独立更新,而 f 记录的是没加 add 的值,当全局加一的时候当前被第二种方式更新的 f 需要消除这个影响,也就是减一

void sol() {
    cin >> n; for(int i = 1; i <= n; i++) cin >> a[i];

    int add = 0, mn = 0;
    memset(f, INF, sizeof f);

    for(int i = 1; i <= n; i++) {
        f[a[i-1]] = min(f[a[i]], 1 + mn) - (a[i] != a[i-1]);
        add += a[i] != a[i-1];
        mn = min(mn, f[a[i-1]]);
    }
    cout << mn + add;
}

法二:更奇怪的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;
}

法三:贪心

咋证啊 没懂

posted @ 2022-05-26 18:28  Bellala  阅读(24)  评论(0)    收藏  举报