P2331 [SCOI2005]最大子矩阵

传送门

DP

注意到m<=2

那就很好搞了

又发现n<=100,k<=10

随便暴力DP都可以了

然后注意一下 可以选空矩阵

状态直接设:f[ i ][ j ][ k ] 表示 第一行 已经选到第 i 个数, 第二行 已经选到第 j 个数,一共选了 k 个矩阵

  那么 f[ i ][ j ][ k ]=max( f[ l ][ j ][ k-1 ]+ Σ(num1[ l ] ~ num1[ i ]) ( 0<=l<=i ) ,

              f[ i ][ l ][ k-1 ]+ Σ(num2[ l ] ~ num2[ i ]) ( 0<=l<=j )   )

  对于宽为2的矩形就特判一下就好了 : 

  if(i == j) f[ i ][ j ][ k ]=max( f[ i ][ j ][ k ],f[ l ][ l ][ k-1 ]+Σ(num1[ l ] ~ num1[ i ] + num2[ l ] ~ num2[ i ]) (0<=l<=i))

顺便写个前缀和 快速求区间值不难吧...

完全可过

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,K;
int sum[107][2],f[107][107][17];
int main()
{
    int a,b;
    scanf("%d%d%d",&n,&m,&K);
    if(m==1)
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a);
            sum[i][0]=sum[i-1][0]+a;
        }
        for(int k=1;k<=K;k++)
            for(int i=1;i<=n;i++)
            {
                f[i][0][k]=f[i-1][0][k];
                for(int j=0;j<=i;j++)
                    f[i][0][k]=max(f[i][0][k],f[j][0][k-1]+sum[i][0]-sum[j][0]);
            }
        printf("%d",f[n][0][K]);
    }
    else
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&a,&b);
            sum[i][0]=sum[i-1][0]+a;
            sum[i][1]=sum[i-1][1]+b;
        }
        for(int k=1;k<=K;k++)
        {
            for(int i=1;i<=n;i++)
            {
                for(int j=1;j<=n;j++)
                {
                    f[i][j][k]=max(f[i-1][j][k],f[i][j-1][k]);
                    for(int l=0;l<=i;l++)
                        f[i][j][k]=max(f[i][j][k],f[l][j][k-1]+sum[i][0]-sum[l][0]);
                    for(int l=0;l<=j;l++)
                        f[i][j][k]=max(f[i][j][k],f[i][l][k-1]+sum[j][1]-sum[l][1]);
                    if(i==j) 
                        for(int l=0;l<=i;l++)
                            f[i][j][k]=max(f[i][j][k],f[l][l][k-1] + (sum[i][0]-sum[l][0]) + (sum[i][1]-sum[l][1]) );
                }
            }
        }
        printf("%d",f[n][n][K]);
    }
    return 0;
}
View Code

 

但是其实有一种更快的方法

这里才是重点

对于前面的方程,每次更新都要把上一步所有的值都找一遍

主要是因为我们不知道这次的矩形要选多长

但是其实如果上一个数被选了,且这个数也要选

那就可以把这两个数合成一个矩形,还可以省一个矩形

实在不行 多出来的矩形也可以选空矩形(可能会选到只剩负数)

所有就有一个比较贪心的思想:

对于每一行数,我们只要注意上一行数的状态

那就不用注意更前面的数了

那么上一步有什么状态呢

注意m最大就是2

所以状态也不多:

设 f[ i ][ j ][ k ]表示选到了第 i 行,已经选了 j 个矩形 , 状态为 k 的最大值

那么当 k=0 就表示 这一行不选

    k=1 就表示 这一行只选左边一个数 

    k=2 就表示 这一行只选右边一个数

    k=3 就表示 这一行选两个数,但是不属于同一个矩形

    k=4 就表示 这一行选两个数,而且是属于同一个矩形

有了状态,转移也就不难了,慢慢推吧...

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;
const int INF=1e9+7;
int n,m,K;
int f[107][17][5];
int main()
{
    int a,b;
    scanf("%d%d%d",&n,&m,&K);

    memset(f,128,sizeof(f));
    for(int i=0;i<=n;i++)
        for(int k=0;k<=K;k++)
            f[i][k][0]=0;

    if(m==1)
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a);//可以边读入边转移
            for(int k=1;k<=K;k++)
            {
                f[i][k][0]=max(f[i-1][k][0],f[i-1][k][1]);
                f[i][k][1]=max(f[i-1][k-1][0],f[i-1][k][1])+a;
                //如果只有一列,那么只有选或不选,转移显然...
            }
        }
        printf("%d",max(f[n][K][0],f[n][K][1]));//对最后一行两个状态取个max,因为不知道哪个状态更好
    }
    else
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&a,&b);//边读入边转移,挺方便的
            for(int k=1;k<=K;k++)
            {
                f[i][k][0]=max( max(f[i-1][k][0],f[i-1][k][1]) , max(f[i-1][k][2],f[i-1][k][3]) );
                f[i][k][0]=max(f[i][k][0],f[i-1][k][4]); //对于不选,那最大值就是上一行所有状态的最大值

                f[i][k][1]=max( max(f[i-1][k-1][0],f[i-1][k][1]) , max(f[i-1][k-1][2],f[i-1][k][3]) ) + a;
                f[i][k][1]=max(f[i][k][1],f[i-1][k-1][4]+a); //注意上一行 k 的变化
                //如果上一行只选左边选了,或者左右两边都选(不是同一个矩形),那就可以直接合并,所以矩形数不用变;
                //如果上一行没选,或者只选右边,或者上一行是一个大矩形,那就要从 k-1 的状态转移过来,因为当前矩形数要比上一行多一

                f[i][k][2]=max( max(f[i-1][k-1][0],f[i-1][k-1][1]) , max(f[i-1][k][2],f[i-1][k][3]) ) + b;
                f[i][k][2]=max(f[i][k][2],f[i-1][k-1][4]+b);
                //跟状态为1的转移差不多...

                f[i][k][3]=max( f[i-1][k-1][1] , max(f[i-1][k-1][2],f[i-1][k][3]) ) + a+b;
                //如果上一行有选一边,那矩形数就只要比上一行多1,如果上一行也是状态3那矩形数就不用变
                if(k>1) f[i][k][3]=max(f[i-1][k-2][0]+a+b , max(f[i][k][3],f[i-1][k-2][4]+a+b));
                //注意如果是从状态 0 或 状态 4 转移过来,没办法合并,那么矩形数就要比上一行多2,要判一下 k 的值是否大于 1

                f[i][k][4]=max( max(f[i-1][k-1][0],f[i-1][k-1][1]) , max(f[i-1][k-1][2],f[i-1][k-1][3]) ) + a+b;
                f[i][k][4]=max(f[i][k][4],f[i-1][k][4]+a+b);
                //除非上一行也是状态 4 ,不然都没法合并,所以其他状态都要从 k-1 转移过来
            }
        }
        int ans=-INF;
        for(int k=0;k<5;k++)
            ans=max(ans,f[n][K][k]);//对最后一行取个max,因为不知道最后一行在状态什么下最优
        printf("%d",ans);
    }
    return 0;
}

 

posted @ 2018-08-28 09:33  LLTYYC  阅读(158)  评论(0编辑  收藏  举报