题目链接
[CSP-S 2024] 染色
题解
这是一道线性 \(dp\) 问题,难点在于在具体的题目背景中抽象出实际问题,最难的地方是分类讨论。
根据题目的意思,如果第 \(i\) 位数字(\(a_{i}\))的颜色和第 \(i\) 位之前的数字(\(a_{[1,i]}\))的颜色都不同,则这个数字贡献为 \(0\),接着,如果前面有相同的颜色,再如果离第 \(i\) 位最近的相同颜色的数字和第 \(i\) 数字相同,则第 \(i\) 位数字贡献为 \(a_{i}\)。我们只讨论这个数可以有贡献时的情况,但这个数无论如何也无法贡献时不进行讨论,我们用结构体存 \(a_{i}\) 这个数字在之前出现过的位置的下标以及这个数字本身:
struct num{ll n,last;
}a[2000005];
当 \(a_{i}\) 在之前没有出现过时,\(a_{i}(last)=0\) ,接下来讨论以下情况
- 两个相同的数相邻(\(a_{i}=a_{i-1}\)),这样的两个数,只要颜色相同,无论如何都会产生贡献,但是对后续有影响(请先看下文)
- 两个相同数不相邻,存在 \(i>j\),\(a_{i}=a_{j}\) ,那么要想让 \(a_{i}\) 产生贡献,\(a_{[i+1,j-1]}\) 区间内的数字必须是同一颜色且和 \(a_{i},_a{j}\) 的颜色不同,那么对于 \(a_{[i+1,j-1]}\) 内的数字,如果存在两个相同的数,但这两个数又不相邻,它们的贡献就没有了。
- 两组相同的数交叉,如果想让两组数都产生贡献,必须满足下列状态:
x,...,y,x,...,y
如果是下面这种状态,只能使其中一组产生贡献:
x,...,y,...,x,...,y
经过上面的分类讨论后,我们设 \(f_{i,j=0/1}\) 表示对于前 \(i\) 个数字,当 \(j=0\),表示第 \(i\) 个数字与第 \(a_{i}(last)\) 不产生贡献时的最优解,当 \(j=1\) 时,表示第 \(i\) 个数字与第 \(a_{i}(last)\) 产生贡献时的最优解。如果两个数字既相邻又相同,要把他们的贡献算进最后的答案里,但是在表示状态时,必须表示成没有产生贡献的状态。如果出现交叉情况,直接访问 \(a_{i}(last)\) 和 \(a_{i}(last)+1\) 两个位置上的数值即可。易得到状态转移方程式:
- 普遍情况:
\( f_{i,0}=\max (f_{i-1,1}\ ,\ f_{i-1,0}) \)
\( f_{i,1}=\max (f_{a_{i}(last),1}\ ,\ f_{{a_{i}(last)},0} \ ,\ f_{a_{i}(last)+1,1}\ ,\ f_{{a_{i}(last)+1},0}) \) - 出现相邻两数的情况时:
\( f_{i,0}=\max (f_{i-1,1}\ ,\ f_{i-1,0}) \)
\( f_{i,1}=f_{i,0} \)
将所有出现过的相邻相同两数加在一起,记为 \(anstmp\) ,最终答案就是 \(\max(f_{n,0} \ ,\ f_{n,1}) \ + \ anstmp\)
CODE
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;ll t,anstmp;
ll dp[2000005][2];
ll lst[2000005];struct num{ll n,last;
}a[2000005];int main(){ll t;scanf("%lld",&t);while (t--){ll n;scanf("%lld",&n);memset(dp,0,sizeof(dp));memset(lst,0,sizeof(lst));anstmp=0;for (ll i=1;i<=n;i++){scanf("%lld",&a[i].n);if (lst[a[i].n]) a[i].last=lst[a[i].n];else a[i].last=0;lst[a[i].n]=i;if (a[i-1].n==a[i].n) anstmp=anstmp+a[i].n;}for (ll i=1;i<=n;i++){if (a[i].n==a[i-1].n){dp[i][0]=max(dp[i-1][0],dp[i-1][1]);dp[i][1]=dp[i-1][1];continue;}else dp[i][0]=max(dp[i-1][0],dp[i-1][1]);if (a[i].last) dp[i][1]=max(max(dp[a[i].last][0],dp[a[i].last][1]),max(dp[a[i].last+1][0],dp[a[i].last+1][1]))+a[i].n;}printf("%lld\n",max(dp[n][0],dp[n][1])+anstmp);}return 0;
}