bzoj1296(SCOI2009)粉刷匠

题目: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;
}

 

posted on 2018-06-06 17:39  Narh  阅读(145)  评论(0编辑  收藏  举报

导航