• BZOJ1084: [SCOI2005]最大子矩阵


    BZOJ1084: [SCOI2005]最大子矩阵

    Description

      这里有一个n*m的矩阵,请你选出其中k个子矩阵,使得这个k个子矩阵分值之和最大。注意:选出的k个子矩阵不能相互重叠。

    Input

      第一行为n,m,k(1≤n≤100,1≤m≤2,1≤k≤10),接下来n行描述矩阵每行中的每个元素的分值(每个元素的分值的绝对值不超过32767)。

    Output

      只有一行为k个子矩阵分值之和最大为多少。

    Sample Input

    3 2 2
    1 -3
    2 3
    -2 3

    Sample Output

    9

    题解Here!

    最大子矩阵嘛。。。
    当然$DP$。。。
    然后看见$min[1,2]$,果断分类讨论:
    • $m==1$:
    普通的最大连续字段和,只不过是$k$个:
    设$dp[i][j][0/1]$表示前$i$个数中取出$j$个矩形的最大和。
    $0/1$表示第$i$GRE数字不选或选。
    转移方程:
    $$dp[i][j][0]=maxleft{egin{array}{}dp[i-1][j][1]\dp[i-1][j][0]end{array} ight.$$
    $$dp[i][j][1]=maxleft{egin{array}{}dp[i-1][j][1]\dp[i-1][j-1][0]end{array} ight.$$
    最后答案就是$max{ dp[n][k][0],dp[n][k][1] }$。
    • $m==2$:
    我们考虑每一行能有什么状态:
    $0$:空出这一行。
    $1$:选择左边空出右边.
    $2$:选择右边空出左边。
    $3$:选择这一行两个,并且不作为一个矩阵,而是左边一列单独一个矩阵,右边单独一个矩阵。
    $4$:选择这一行两个,并且两个一块作为一个矩阵的一部分。
    定义$dp[i][j][k]$为当前处理到第$i$行,已经选了$j$个矩阵,当前行状态为$k$的最大值,$kin[0,4]$。
    1. 如果空出这一行,则$j$不需要变化,直接继承上一行的各种状态的最大值即可:
    $$dp[i][j][0]=maxleft{egin{array}{}dp[i-1][j][0]\dp[i-1][j][1]\dp[i-1][j][2]\dp[i-1][j][3]\dp[i-1][j][4]end{array} ight.$$
    2. 如果选择左边空出右边:
    如果上一行的左边没有单独地选择成为矩阵,即选择$1$或$3$,则$j$需要包含新选择成为的矩阵。
    即这一行的左边的这个矩阵。
    如果上一行为同时选择两列的为一个矩阵的状态,则只选择单独的左边是不能包含进去上一行的矩阵的,所以也应$j-1$。
    设这一行左边的值为$v_1$,则有转移方程:
    $$dp[i][j][1]=maxleft{egin{array}{}dp[i-1][j-1][0]\dp[i-1][j][1]\dp[i-1][j-1][2]\dp[i-1][j][3]\dp[i-1][j-1][4]end{array} ight}+v_1$$
    3. 右边同理:
    设这一行右边的值为$v_2$,则有转移方程:
    $$dp[i][j][2]=maxleft{egin{array}{}dp[i-1][j-1][0]\dp[i-1][j-1][1]\dp[i-1][j][2]\dp[i-1][j][3]\dp[i-1][j-1][4]end{array} ight}+v_2$$
    4. 选择两个分别单独作为矩阵,类似只选择左边或右边,不过是单独选左边和右边合并了下:
    $$dp[i][j][3]=maxleft{egin{array}{}dp[i-1][j-1][1]\dp[i-1][j-1][2]\dp[i-1][j][3]\dp[i-1][j-2][4]quad jgeq 2end{array} ight}+v_1+v_2$$
    5. 选择两个作为一个矩阵,则上一行除了可以接上的,都得$j-1$:
    $$dp[i][j][4]=maxleft{egin{array}{}dp[i-1][j-1][0]\dp[i-1][j-1][1]\dp[i-1][j-1][2]\dp[i-1][j-1][3]\dp[i-1][j][4]end{array} ight}+v_1+v_2$$
    好了,这个题就这么做完了。
    真是恶心啊。。。
    然后这个题如果数据范围再大一点,涉及到卡常的话,有个小优化:
    当我们的程序要多次调用$dp[i][j][k]$并且只有$k$在疯狂变化时,我们可以预先把$dp[i][j]$当成指针存入*$p$中。
    这样我们就不用每次都计算三维的$dp[i][j][k]$,而是直接调用$p[k]$。
    这个优化可以疯狂卡常。。。
    因为$C++$中查询一个多维的数组,要先进行复杂的乘法计算,然后再取地址,调用值。
    这个优化直接把这个每次计算省掉了。
    不过对于这个题好像没有什么用。。。
    附代码:
    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstring>
    #define MAXN 110
    using namespace std;
    int n,m,q,ans;
    int val[MAXN][3],dp[MAXN][12][5];
    inline int read(){
    	int date=0,w=1;char c=0;
    	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
    	return date*w;
    }
    void solve_one(){
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=q;j++){
    		dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0])+val[i][1];
    		dp[i][j][0]=max(dp[i-1][j][1],dp[i-1][j][0]);
    	}
    	ans=max(dp[n][q][0],dp[n][q][1]);
    	printf("%d
    ",ans);
    }
    void solve_two(){
    	memset(dp,-0x3f3f3f3f,sizeof(dp));
    	for(int i=0;i<=n;i++)for(int j=0;j<=q;j++)dp[i][j][0]=0;
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=q;j++){//恶心的转移。。。
    		dp[i][j][0]=max(dp[i-1][j][0],max(max(dp[i-1][j][1],dp[i-1][j][2]),max(dp[i-1][j][3],dp[i-1][j][4])));
    		
    		dp[i][j][1]=max(dp[i-1][j-1][0],max(max(dp[i-1][j][1],dp[i-1][j-1][2]),max(dp[i-1][j][3],dp[i-1][j-1][4])))+val[i][1];
    		
    		dp[i][j][2]=max(dp[i-1][j-1][0],max(max(dp[i-1][j-1][1],dp[i-1][j][2]),max(dp[i-1][j][3],dp[i-1][j-1][4])))+val[i][2];
    		
    		dp[i][j][3]=max(dp[i-1][j-1][1],max(dp[i-1][j-1][2],dp[i-1][j][3]))+val[i][1]+val[i][2];
    		if(j>=2)dp[i][j][3]=max(dp[i][j][3],dp[i-1][j-2][4]+val[i][1]+val[i][2]);
    		
    		dp[i][j][4]=max(dp[i-1][j-1][0],max(max(dp[i-1][j-1][1],dp[i-1][j-1][2]),max(dp[i-1][j-1][3],dp[i-1][j][4])))+val[i][1]+val[i][2];
    	}
    	ans=max(dp[n][q][0],max(max(dp[n][q][1],dp[n][q][2]),max(dp[n][q][3],dp[n][q][4])));
    	printf("%d
    ",ans);
    }
    void work(){
    	if(m==1)solve_one();
    	else solve_two();
    }
    void init(){
    	n=read();m=read();q=read();
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=m;j++)
    	val[i][j]=read();
    }
    int main(){
    	init();
    	work();
        return 0;
    }
    
  • 相关阅读:
    c# 第41节 异常处理
    c# 第40节 密封类、密封方法
    c# 第39节 抽象类、抽象方法
    c# 第38节 接口的实现
    c# 第37节 接口的实现与继承
    c# 第36节 接口的声明
    测试面试题集-接口测试
    Python接口自动化测试系列文章汇总
    Jmeter系列之简介与环境安装
    Python接口自动化之logging封装及实战
  • 原文地址:https://www.cnblogs.com/Yangrui-Blog/p/9867505.html
Copyright © 2020-2023  润新知