CF2172L Maximum Color Segment 题解

· · 题解

题目传送门:CF2172L Maximum Color Segment

题意简述

给出长度为 n 的双色串,你可以进行至多 m 次操作,每次操作可以选出一个长度恰好为 k 的子串进行颜色反转,最大化其颜色段数。

题目分析

几个套路结论说是。首先极长段数等于邻项异或;其次奇偶项反转,等于统计邻项同色;然后做异或差分,等于统计 0 的数量;操作转化为了 i 和 i+k 同时反转,于是最后将模 k 的同余类拎出来,等于邻项同时反转。注意到 0,n 所在的两个同余类是特殊的,对其分讨一下边界,然后做背包 DP。

记拎出来的同余类序列为 $b$,则有 $f_{i,j,b_i} \leftarrow \max(f_{i-1,j,0},f_{i-1,j,1})$,$f_{i,j,\neg b_i} \leftarrow \max(f_{i-1,j-1,0}-1,f_{i-1,j-1,1}+1)$,然后 $f_{i,j,0}$ 加 $1$。 最后再用一个分组背包把所有同余类整合起来,$g_i \leftarrow g_{i-j} + \max(f_{j,0},f_{j,1})$,取 $0 \sim m$ 的最大值即可。 卡紧枚举上界,时间复杂度 $O(n^2)$,空间复杂度 $O(n)$。认为 $m$ 不比 $n$ 大。 ### 代码实现 ```cpp #include<bits/stdc++.h> using namespace std; constexpr int N=3003; int n,m,k,tot,ans,f[N][2],g[N]; bool a[N],b[N]; void solve(){ for(int i=1;i<tot;i++){ for(int j=tot;j;j--) f[j][b[i]]=max(f[j][0],f[j][1]), f[j][!b[i]]=max(f[j-1][0]-1,f[j-1][1]+1); f[0][b[i]]=max(f[0][0],f[0][1]); for(int j=b[i];j<=tot;j++)f[j][0]++; // 避免初始为 1 时 f[0][0] 污染 } for(int i=m;i>=0;i--) for(int j=0;j<=min(i,tot);j++) g[i]=max(g[i],g[i-j]+max(f[j][0],f[j][1])); } int main(){ cin>>n>>m>>k; for(int i=0;i<n;i++){ char c; cin>>c; a[i]=(c=='B')^(i&1); } for(int i=n;--i;)a[i]^=a[i-1]; { tot=0; for(int i=k;i<n;i+=k)b[tot++]=a[i]; memset(f,-0x3f,(tot+1)*8),f[0][b[0]]=!b[0],f[1][!b[0]]=b[0]; if(tot)solve(); // 注意要判空 } if(n%k){ tot=0; for(int i=n-k;i>0;i-=k)b[tot++]=a[i]; memset(f,-0x3f,(tot+1)*8),f[0][b[0]]=!b[0],f[1][!b[0]]=b[0]; solve(); } for(int now=1;now<k;now++) if(now!=n%k){ tot=0; for(int i=now;i<n;i+=k)b[tot++]=a[i]; memset(f,-0x3f,(tot+1)*8),f[0][b[0]]=!b[0]; solve(); } for(int i=0;i<=m;i++)ans=max(ans,g[i]); cout<<ans+1; return 0; } ``` [AC 记录](https://codeforces.com/contest/2172/submission/398088717)。