洛谷P11233 [CSP-S 2024] 染色
P11233 [CSP-S 2024] 染色
题目描述
给定一个长度为 \(n\) 的正整数数组 \(A\),其中所有数从左至右排成一排。
你需要将 \(A\) 中的每个数染成红色或蓝色之一,然后按如下方式计算最终得分:
设 \(C\) 为长度为 \(n\) 的整数数组,对于 \(A\) 中的每个数 \(A_i\)(\(1 \leq i \leq n\)):
- 如果 \(A_i\) 左侧没有与其同色的数,则令 \(C_i = 0\)。
- 否则,记其左侧与其最靠近的同色数为 \(A_j\),若 \(A_i = A_j\),则令 \(C_i = A_i\),否则令 \(C_i = 0\)。
你的最终得分为 \(C\) 中所有整数的和,即 \(\sum \limits_{i=1}^n C_i\)。你需要最大化最终得分,请求出最终得分的最大值。
输入格式
本题有多组测试数据。
输入的第一行包含一个正整数 \(T\),表示数据组数。
接下来包含 \(T\) 组数据,每组数据的格式如下:
第一行包含一个正整数 \(n\),表示数组长度。
第二行包含 \(n\) 个正整数 \(A_1, A_2, \dots, A_n\),表示数组 \(A\) 中的元素。
输出格式
对于每组数据:输出一行包含一个非负整数,表示最终得分的最大可能值。
输入输出样例 #1
输入 #1
3
3
1 2 1
4
1 2 3 4
8
3 5 2 5 1 2 1 4
输出 #1
1
0
8
说明/提示
【样例 1 解释】
对于第一组数据,以下为三种可能的染色方案:
- 将 \(A_1, A_2\) 染成红色,将 \(A_3\) 染成蓝色,其得分计算方式如下:
- 对于 \(A_1\),由于其左侧没有红色的数,所以 \(C_1 = 0\)。
- 对于 \(A_2\),其左侧与其最靠近的红色数为 \(A_1\)。由于 \(A_1 \neq A_2\),所以 \(C_2 = 0\)。
- 对于 \(A_3\),由于其左侧没有蓝色的数,所以 \(C_3 = 0\)。
该方案最终得分为 \(C_1 + C_2 + C_3 = 0\)。
- 将 \(A_1, A_2, A_3\) 全部染成红色,其得分计算方式如下:
- 对于 \(A_1\),由于其左侧没有红色的数,所以 \(C_1 = 0\)。
- 对于 \(A_2\),其左侧与其最靠近的红色数为 \(A_1\)。由于 \(A_1 \neq A_2\),所以 \(C_2 = 0\)。
- 对于 \(A_3\),其左侧与其最靠近的红色数为 \(A_2\)。由于 \(A_2 \neq A_3\),所以 \(C_3 = 0\)。
该方案最终得分为 \(C_1 + C_2 + C_3 = 0\)。
- 将 \(A_1, A_3\) 染成红色,将 \(A_2\) 染成蓝色,其得分计算方式如下:
- 对于 \(A_1\),由于其左侧没有红色的数,所以 \(C_1 = 0\)。
- 对于 \(A_2\),由于其左侧没有蓝色的数,所以 \(C_2 = 0\)。
- 对于 \(A_3\),其左侧与其最靠近的红色数为 \(A_1\)。由于 \(A_1 = A_3\),所以 \(C_3 = A_3 = 1\)。
该方案最终得分为 \(C_1 + C_2 + C_3 = 1\)。
可以证明,没有染色方案使得最终得分大于 \(1\)。
对于第二组数据,可以证明,任何染色方案的最终得分都是 \(0\)。
对于第三组数据,一种最优的染色方案为将 \(A_1, A_2, A_4, A_5, A_7\) 染为红色,将 \(A_3, A_6, A_8\) 染为蓝色,其对应 \(C = [0, 0, 0, 5, 0, 2, 1, 0]\),最终得分为 \(8\)。
【样例 2】
见选手目录下的 color/color2.in 与 color/color2.ans。
【数据范围】
对于所有测试数据,保证:\(1\leq T\leq 10\),\(2\leq n\leq 2\times 10^5\),\(1\leq A_i\leq 10^6\)。
| 测试点 | \(n\) | \(A_i\) |
|---|---|---|
| \(1\sim 4\) | \(\leq 15\) | \(\leq 15\) |
| \(5\sim 7\) | \(\leq 10^2\) | \(\leq 10^2\) |
| \(8\sim 10\) | \(\leq 2000\) | \(\leq 2000\) |
| \(11,12\) | \(\leq 2\times 10^4\) | \(\leq 10^6\) |
| \(13\sim 15\) | \(\leq 2\times 10^5\) | \(\leq 10\) |
| \(16\sim 20\) | ^ | \(\leq 10^6\) |
解题报告
一道巧妙的DP题目
这里推荐去做做UVA12991 Game Rooms - 洛谷,在DP过程的实现上有一点相似之处。
我们将红色和蓝色用 \(0/1\) 代替,和 UVA12991 一样,我们集中精力去对一段连续的极长 \(0/1\) 段进行研究。
首先应该想到,我们只关心颜色的异同,所以两种颜色实际上是等价的,不需要专门区分对待。
于是设 \(dp[i]\) 表示在区间 \([1,i]\) 中以 \(i\) 为极长段的结尾的后一位时的最大价值,我们以区间DP的方式去思考怎么进行状态转移。
首先,采用 UVA12991 的方法,我们枚举以 \(i\) 为结尾的极长段的开头的前一位 \(j\),尝试去快速计算区间 \([j,i]\) 的价值。
(这里可能会有点绕,实际上就是对于区间 \([j,i]\),有\(A_j==A_i\),并且区间 \([j+1,i-1]\) 中的颜色都相同,并且不与 \(i\) 和 \(j\) 相同)
考虑怎么转移。
最简单的情况:如果 \(i\) 不产生贡献,直接从 \(dp[i-1]\) 转移过来就好了。
考虑 \(i\) 产生贡献的情况。很显然,如果 \(A_j \ne A_i\),那么 \(i\) 就不可能产生贡献。同时,设\(lst_i\) 为区间 \([1,i-1]\) 的中最后一位相同的数的位置,如果不选 \(lst_i\) 为区间的开头 \(j\),那么就一定可以在 \([j,i]\) 中找到至少找到一个与之相同的位置 \(k\),设这个相同的数为 \(x\),那么将 \(i,k,j\) 染成同一个颜色后,就有两个极长段 \(j,k\) 和 \(k,i\),得到两倍的 \(x\),比选择极长段 \([j,i]\) 得到一个 \(x\) 更优。
所以,我们应该记下每个 \(i\) 对应的 \(lst_i\),计算极长段 \([lst_i,i]\)价值,并累计上之前的价值。
怎么累计上之前的价值?注意并不是累计上 \(dp[lst_i]\),而是 \(dp[lst_i+1]\)。为什么?
因为此时有 \(A_{lst_i}=A_i \ne A_k,k \in [j+1,i-1]\),那么对于 \(lst_i+1\) 来说,实际上形成了一段以 \(lst_i\) 为结尾的极长段,这时候 \(lst_i+1\) 就可以产生价值 \(dp[lst_{i}+1]\),那么我们就需要累计上这个价值,同时,容易知道,\(lst_i+1\) 的价值包含了 \(lst_i\) 的价值,因为我们一开始就有 \(dp[lst_{i}+1]=dp[lst_i]\)。
那么问题就只剩怎么计算区间 \([j+1,i-1]\) 的价值,显然,由于区间 \([j+1,i-1]\) 的颜色一样,当相邻的两个数相同时就统计这个数,容易想到可以用前缀和来优化,代码具体为:
for(int i=1;i<=n;i++)
{
w[i]=read();// w数组就是题目中的A数组
s[i]=s[i-1]+w[i]*(w[i]==w[i-1]);// 前缀和
}
这题到这里就解决了,复杂度 \(O(Tn)\),还有一些细节就注释在代码里了,如下:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF=0x3f3f3f3f;
const int N=1001100;
// 实际上只有g数组需要1E+6的大小,其他数组只需2E+5
#define ckmax(x,y) ( x=max(x,y) )
#define ckmin(x,y) ( x=max(x,y) )
inline int read()
{
int f=1,x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar(); }
return f*x;
}
int n,dp[N];
int w[N],s[N];
int lst[N],g[N];
signed main()
{
int Q=read();
while(Q--)
{
memset(g,0,sizeof(g));
memset(dp,0,sizeof(dp));
memset(lst,0,sizeof(lst));
n=read();
for(int i=1;i<=n;i++)
{
w[i]=read();
lst[i]=g[w[i]]; g[w[i]]=i;
s[i]=s[i-1]+w[i]*(w[i]==w[i-1]);
}
for(int i=1;i<=n;i++)
{
dp[i]=dp[i-1];
if(lst[i])// 先判断有没有相同的数
ckmax(dp[i],dp[lst[i]+1]+w[i]+s[i-1]-s[lst[i]]);
// 注意s[lst[i]]不要打成s[lst[i]-1]
}
printf("%lld\n",dp[n]);
}
return 0;
}

浙公网安备 33010602011771号