洛谷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 解释】

对于第一组数据,以下为三种可能的染色方案:

  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\)
  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\),其左侧与其最靠近的红色数为 \(A_2\)。由于 \(A_2 \neq A_3\),所以 \(C_3 = 0\)
    该方案最终得分为 \(C_1 + C_2 + C_3 = 0\)
  1. \(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;
}
posted @ 2025-09-04 16:03  南北天球  阅读(97)  评论(0)    收藏  举报