Luogu P11233 CSP-S2024 染色 题解 [ 蓝 ] [ 线性 dp ] [ 前缀和优化 ]
染色:傻逼题。
赛时没切染色的都是唐氏!都是唐氏!都是唐氏!都是唐氏!都是唐氏!都是唐氏!都是唐氏!
包括我。
真的太傻逼了这题。
我今晚心血来潮一打这题,随便优化一下,就 AC 了。
怎么做到这么蠢的啊我。
暴力 dp
不要从子串的角度去考虑,我们对每一个数进行考虑 dp,否则会和我在场上一样想成用区间设计 dp,并且只能打出 \(O(n^4)\) 的暴力出来。
设计 \(dp_{i}\) 表示考虑到第 \(i\) 个数的最大值。
这里不用设计两种颜色的状态,因为他们本质上都是一样的,我们分段时只注重前一段的开头的前一个数,仅此而已。
那么很显然就可以打个 \(n^2\) 的暴力了吧?然后 \(50pts\) 到手,然后我考场上没想出来,太傻逼了。
值域优化
显然,我们不用枚举前一段的开头的前一个数在哪,只需要知道它的值是什么就好了。这样就能根据前缀取个最大值进行 dp 了。
设计 \(dp_{i,v}\) 表示考虑到第 \(i\) 个数,本段区间的开头的前一个数是 \(v\) 时的最大答案。为啥是本段呢,因为下一段转移的时候,这里的本段就成了前一段。
于是显然就维护一个线段树转移一下就好了啊。
有两种转移,第一个是同色转移,第二个是不同色转移。
同色转移
条件:\(a_i=a_{i-1}\)。
我们只要给线段树上每一个点加上 \(a_i\) 即可。
异色转移
条件:无。
显然的转移:
就是要么这一段开头与前一段开头的前一个数不相等,挑一个最大值转移,要么就是相等,加上贡献即可,注意由于之前进行过同色转移,要减掉这一部分的贡献。(可以证明同色转移之后一定是最大的,因为毕竟都把之前所有值的最大值加了 \(a_i\) 嘛)
时间 \(O(t n \log V)\),有几率被卡。
数组优化
观察到只有全局最大值,全局加,单点修改操作,于是直接把线段树换成数组省去一个老哥即可。
全局加直接打懒标记就好。
由于懒标记一视同仁,所以比较加懒标记之前的 dp 值转移,输出时加上懒标记即可。
时间 \(O(tV)\),单组数据时间复杂度瓶颈在于初始化。
代码
/*
定义 dp_i,0/1 表示第 i 个数染色为 1 或 0 时的最大得分。
我们分别维护每种元素在考虑到第 i 个元素时充当上一段段尾的最大得分。
和上个数同色时:
当 i=i-1 时,此时前面的数里面全部加该点的点权。
否则不做任何改动。
这个数作为新开头:
修改上一个元素在 dp 里的值。
转移是 dp_{i-1}=max(allmax,a_i+dp_{i}-[i==i-1]*a[i])
*/
#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pi;
ll n,a[200005],dp[1000005],add,mx=0;
void solve()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
memset(dp,-0x3f,sizeof(dp));
add=0;
mx=0;
for(int i=1;i<=n;i++)
{
if(a[i]==a[i-1])add+=a[i];
dp[a[i-1]]=max(mx,a[i]+dp[a[i]]-(a[i]==a[i-1])*a[i]);
mx=max(mx,dp[a[i-1]]);
}
cout<<mx+add<<'\n';
}
int main()
{
//freopen("color.in","r",stdin);
//freopen("color.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--)solve();
return 0;
}