CSP-S 2024 T3 染色 题解
一道自认为特别好的 dp 题,虽然水,但是感觉很适合刚学 dp 的人做的一道题。
题目链接:https://www.luogu.com.cn/problem/P11233。
考虑 dfs 暴力,枚举每一个位置的颜色,记录当前位置上一个的红色和蓝色的数的值,在中途统计答案。时间复杂度:\(O(2^n)\)。
考虑将 dfs 暴力转化为 dp,将 dfs 中记录的状态转化为 \(f_{i,j,k}\),表示选到第 \(i\) 个,从第 \(i\) 个数起,从右往左第一个红色数为 \(j\),第一个蓝色数为 \(k\) 的最大代价,转移和 dfs 基本一样。时间复杂度:\(O(n^3)\)。
容易发现一个性质,我们没必要将 \(j\) 和 \(k\) 都记录下来,因为 \(j\) 和 \(k\) 中一定有一个为 \(a_i\),这样又可以把状态设计为 \(f_{i,j,0/1}\) 表示选到第 \(i\) 个数,第 \(i\) 个数的颜色为 \(0/1\)(红或蓝),上一个与第 \(i\) 个数异色的数为 \(j\) 的最大代价。转移方程为:
\[f_{i,j,o} \gets f_{i-1,j,o}+a_i[a_i=a_{i-1}]
\]
\[f_{i,a_{i-1},o} \gets \max_j \{f_{i-1,j,\neg o} + j[j=a_i]\}
\]
时间复杂度:\(O(n^2)\)。
将第二个转移式拆分为两个:
\[f_{i,a_{i-1},o}\gets \max_j f_{i-1,j,\neg o}
\]
\[f_{i,a_{i-1},o}\gets f_{i-1,a_i,1}+a_i
\]
对于这三个转移式,我们首先可以将 \(i\) 通过滚动数组滚掉,然后可以维护一个全局 \(\max\) 和一个全局加的 tag,这样就可以做到 \(O(n)\) 的转移了。
时间复杂度:\(O(n)\)。
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 1e6 + 5;
int T, n, maxa, a[kMaxN];
long long f[kMaxN][2], ans, g[2], h[2];
int main() {
ios::sync_with_stdio(0), cin.tie(0);
for (cin >> T; T; T--, maxa = ans = 0) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i], maxa = max(maxa, a[i]);
}
for (int i = 0; i <= maxa; i++) {
f[i][0] = f[i][1] = -1e18;
}
f[0][0] = f[0][1] = g[0] = g[1] = h[0] = h[1] = 0;
for (int i = 1; i <= n; i++) {
long long tmp[2] = {};
for (int o = 0; o < 2; o++) {
tmp[o] = max({f[a[i - 1]][o], g[o ^ 1] - a[i] * (a[i] == a[i - 1]), f[a[i]][o ^ 1] + a[i] - a[i] * (a[i] == a[i - 1])});
}
for (int o = 0; o < 2; o++) {
h[o] += a[i] * (a[i] == a[i - 1]), g[o] = max(g[o], f[a[i - 1]][o] = tmp[o]);
}
}
for (int i = 0; i <= maxa; i++) {
ans = max(ans, max(f[i][0] + h[0], f[i][1] + h[1]));
}
cout << ans << '\n';
}
return 0;
}
水题,但是考试上没有细想,以为题目太难,直接开始打 T4 暴力,如果仔细想一下的话,肯定是能够想出来的。