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)\}\)。然后有两个结论:
- 对 \(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\)
- 对 \(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;
}
法三:贪心
咋证啊 没懂

浙公网安备 33010602011771号