这道题目一开始直接写了个O(n^3*m)的dp上去,居然过了。
题目大意:
有N块木板,每块木板长度都为M,把每块木板分为M个格子,每个格子能涂红色或蓝色,每个格子只能涂一次。每次涂只能选一段连续的区域涂。现在已经给定了每块木板格子上想要的颜色,问给T次涂色机会,一共可以让多少个格子能够涂出想要的颜色。
N,M < 50,T < 2500
思路:
一开始想了个三维的dp,dp[i][j][k]表示前i块木板且当前木板前j个格子涂了k次的最大值。
那么就有:
dp[i][j][k]=dp[i][t][k-1]+max(sum[i][j]-sum[i][t],j-t-(sum[i][j]-sum[i][t])) //当前木板
dp[i][0][k]=dp[i-1][m][k-1]+max(sum[i][j],j-sum[i][j]) //上一木板的答案转移过来
由于中间要枚举一下当前木板的上一个格子位置,因此会多一个for,这样做的复杂度是O(n^3*m),本题中大概是3 * 10^8,居然卡过去了,跑了500多ms。
代码:
#include<bits/stdc++.h> using namespace std; int n,m,T,sum[55][55]; long long f[55][55][2555]; string s[55]; int main() { cin>>n>>m>>T; for(int i=1;i<=n;i++)cin>>s[i]; for(int i=1;i<=n;i++){ for(int j=0;j<m;j++){ if(s[i][j]=='1')sum[i][j+1]=sum[i][j]+1; else sum[i][j+1]=sum[i][j]; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ for(int k=1;k<=T;k++){ for(int t=0;t<j;t++){ int tmp=sum[i][j]-sum[i][t]; if(t==0)f[i][j][k]=f[i-1][m][k-1]+max(tmp,j-tmp); else f[i][j][k]=max(f[i][j][k],f[i][t][k-1]+max(tmp,j-t-tmp)); } } } } cout<<f[n][m][T]<<endl; }
看了下题解,用了两个dp。
其实在我一开始写的时候,突然觉得我自己的那个dp好像只能计算当前木板的情况,后来发现可以从上一块转移过来。
在题解中,则是采用计算当前木板的情况,这样子涂色的情况就最多只有m种。
可以令f[i][j][k]表示第i块木板涂了j个格子用了k次机会的最大值。
显然转移方程同上面那个几乎一样,只不过不需要从上一个木板转移过来。
然后可以把一块木板上的m种情况看成一组中的m个物品,每个物品的费用和价值分别为x和f[i][m][x],问题变成了从N个组中每个组选1个放到容量为T的背包中让总价值最大。
也就是分组背包了。
这样子主要的复杂度为O(nmm*m)
代码:
#include<bits/stdc++.h> using namespace std; int n,m,T,sum[55][55]; long long f[55][55][55],dp[2555]; char s[55][55]; int main() { cin>>n>>m>>T; for(int i=1;i<=n;i++)scanf("%s",s[i]); for(int i=1;i<=n;i++){ for(int j=0;j<m;j++){ if(s[i][j]=='1')sum[i][j+1]=sum[i][j]+1; else sum[i][j+1]=sum[i][j]; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ for(int k=1;k<=min(m,j);k++){ for(int t=0;t<j;t++){ int tmp=sum[i][j]-sum[i][t]; f[i][j][k]=max(f[i][j][k],f[i][t][k-1]+max(tmp,j-t-tmp)); } } } } for(int i=1;i<=n;i++){ for(int j=T;j>=0;j--){ for(int k=1;k<=min(m,j);k++){ dp[j]=max(dp[j],dp[j-k]+f[i][m][k]); } } } cout<<dp[T]<<endl; }
翻看题解的时候还看到大佬用4维dp[i][j][k][0/1]表示前i块木板前j个格子涂了k次并且当前这次涂0或者涂1的最大值。
有了最后一维的记录,我们就可以直接从上一个格子的状态转移过来。空间换时间血赚。
代码:
#include<bits/stdc++.h> using namespace std; int n,m,T,sum[55][55]; long long f[55][55][2555][2]; char s[55][55]; int main() { cin>>n>>m>>T; for(int i=1;i<=n;i++)scanf("%s",s[i]); for(int i=1;i<=n;i++){ for(int j=0;j<m;j++){ if(s[i][j]=='1')sum[i][j+1]=sum[i][j]+1; else sum[i][j+1]=sum[i][j]; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ for(int k=1;k<=T;k++){ for(int t=0;t<=1;t++){ if(j==1){ f[i][j][k][t]=max(f[i-1][m][k-1][0],f[i-1][m][k-1][1])+(s[i][j-1]==t+'0'); continue; } f[i][j][k][t]=max(f[i][j-1][k][t],f[i][j-1][k-1][t^1])+(s[i][j-1]==t+'0'); } } } } cout<<max(f[n][m][T][0],f[n][m][T][1])<<endl; }