题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1296
这道题暴露出自己:
1.对于区间与前缀的可转化性认识不足;
2.对于分组背包不够熟练。
很容易想到最后是一个分组背包,枚举总共刷 k 次,前几个木条刷了 k - j 次,当前木条刷 j 次,得到答案。
所以我们需要每个木条“刷k次的最大正确数量”这一个值。
自己只会做“前缀被全刷完的最少次数”(颜色相同就-1),于是想把它转化成这个问题,就是算出前缀被刷完的最小次数,用它更新“刷k次的最大数量”。
但是又觉得不一定是前缀被刷完,可以是区间被刷完。于是尝试把那个写法搬到区间上;当然很混乱。
1.其实“被刷完”这个状态不够灵活,不如就定义成“刷k次的最大数量”;只要想到(2)和(3),就知道这个定义还是可以转移的。
2.如果是这个定义,用前缀的状态递推就足够,因为不一定每个都刷了颜色,就不用刻意分区间了。
3.在求前缀的时候就是指定后 j 个只刷一次。这种划分的方法应该很熟悉它的正确性才行。
4.分组背包可以在边算dp[i]的时候就边维护好。就算不这样而最后单独求一下,其实也挺好弄的。自己应该对它更熟练。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=55,T=2505; int n,m,t,a[N][N],dp[N][T],s[N],ans,f[N][T]; int cal(int x,int y) { int a=s[y]-s[x-1]; int b=y-x+1-a; return max(a,b); } int main() { scanf("%d%d%d",&n,&m,&t); for(int i=1;i<=n;i++) { memset(s,0,sizeof s); // memset(dp,0,sizeof dp); for(int j=1;j<=m;j++) { scanf("%1d",&a[i][j]);s[j]=s[j-1]+(a[i][j]); for(int k=1;k<=j;k++) { dp[j][k]=0; for(int l=0;l<j;l++) dp[j][k]=max(dp[j][k],dp[l][k-1]+cal(l+1,j)); } } for(int k=1;k<=t;k++) //k<=t for(int j=0;j<=k&&j<=m;j++) f[i][k]=max(f[i][k],f[i-1][k-j]+dp[m][j]); } for(int i=1;i<=t;i++)ans=max(ans,f[n][i]); printf("%d",ans); return 0; }