P4158 [SCOI2009]粉刷匠
dp题啊,俗话说dp有两难,一者为找状态,另一者为状态转移,但是
前者找不到,显然后者更不会啊!不过好在这是道线性dp的题,线性dp
主要看你最后想要的状态,实际上可以根据你想要的状态倒推你需
要什么状态,不如让我们切入正题。这道题你想知道全部的木块最
多有多少是正确染色的,因此你需要找切入点了,我需要判断前面
一些的状态。这时候你发现每一行都是独立的所以每一行都应该作
为状态。还需要考虑每次染色的独立性。因此我们用f[i][j]表示
前行共染了j次的最大正确数。
然后我陷入了深思(然后呢)好吧人类智慧来了
既然我已经确定了前i行的状态,我怎么转移呢,转念一想似乎需要
确定每一行的转移。因此诞生了g数组。我用g[i][j][k]表示前i行
用了j次粉刷机会,涂了k个颜色块。所以呢?如何状态转移呢?
for(int i=1;i<=n;i++)
for(int j=1;j<=t;j++)
for(int k=1;k<=min(j,m);k++)
f[i][j]=max(f[i][j],f[i-1][j-k]+g[i][k][m]);
对,我们处理了f数组,g数组也需要处理啊!
人类智慧又来了
g数组的处理又将何去何从。其实可以这样
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=m;k++)
for(int q=j-1;q<k;q++)
g[i][j][k]=max(g[i][j][k],g[i][j-1][q]+max(sum[i][k]-sum[i][q],k-q-sum[i][k]+sum[i][q]));
而sum数组就是染成蓝色的数量。这道题就可以结束了。dp题的套路也就结束了。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
int t,n,m;
char s[60];
int sum[60][60],g[60][60][60],f[60][3000],ans;
int main(){
cin>>n>>m>>t;
for(int i=1;i<=n;i++){
sum[i][0]=0;
scanf("%s",s+1);
for(int j=1;j<=m;j++){
if(s[j]-'0'==1) sum[i][j]=sum[i][j-1]+1;
else sum[i][j]=sum[i][j-1];
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=m;k++)
for(int q=j-1;q<k;q++)
g[i][j][k]=max(g[i][j][k],g[i][j-1][q]+max(sum[i][k]-sum[i][q],k-q-sum[i][k]+sum[i][q]));
for(int i=1;i<=n;i++)
for(int j=1;j<=t;j++)
for(int k=1;k<=min(j,m);k++)
f[i][j]=max(f[i][j],f[i-1][j-k]+g[i][k][m]);
ans=0;
for(int i=1;i<=t;i++) ans=max(ans,f[n][i]);
cout<<ans<<endl;
return 0;
}