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$。。。
然后看见$m\in[1,2]$,果断分类讨论:
  • $m==1$:
普通的最大连续字段和,只不过是$k$个:
设$dp[i][j][0/1]$表示前$i$个数中取出$j$个矩形的最大和。
$0/1$表示第$i$GRE数字不选或选。
转移方程:
$$dp[i][j][0]=\max\left\{\begin{array}{}dp[i-1][j][1]\\dp[i-1][j][0]\end{array}\right.$$
$$dp[i][j][1]=\max\left\{\begin{array}{}dp[i-1][j][1]\\dp[i-1][j-1][0]\end{array}\right.$$
最后答案就是$\max\{\ dp[n][k][0],dp[n][k][1]\ \}$。
  • $m==2$:
我们考虑每一行能有什么状态:
$0$:空出这一行。
$1$:选择左边空出右边.
$2$:选择右边空出左边。
$3$:选择这一行两个,并且不作为一个矩阵,而是左边一列单独一个矩阵,右边单独一个矩阵。
$4$:选择这一行两个,并且两个一块作为一个矩阵的一部分。
定义$dp[i][j][k]$为当前处理到第$i$行,已经选了$j$个矩阵,当前行状态为$k$的最大值,$k\in[0,4]$。
1. 如果空出这一行,则$j$不需要变化,直接继承上一行的各种状态的最大值即可:
$$dp[i][j][0]=\max\left\{\begin{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}\right.$$
2. 如果选择左边空出右边:
如果上一行的左边没有单独地选择成为矩阵,即选择$1$或$3$,则$j$需要包含新选择成为的矩阵。
即这一行的左边的这个矩阵。
如果上一行为同时选择两列的为一个矩阵的状态,则只选择单独的左边是不能包含进去上一行的矩阵的,所以也应$j-1$。
设这一行左边的值为$v_1$,则有转移方程:
$$dp[i][j][1]=\max\left\{\begin{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}\right\}+v_1$$
3. 右边同理:
设这一行右边的值为$v_2$,则有转移方程:
$$dp[i][j][2]=\max\left\{\begin{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}\right\}+v_2$$
4. 选择两个分别单独作为矩阵,类似只选择左边或右边,不过是单独选左边和右边合并了下:
$$dp[i][j][3]=\max\left\{\begin{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 j\geq 2\end{array}\right\}+v_1+v_2$$
5. 选择两个作为一个矩阵,则上一行除了可以接上的,都得$j-1$:
$$dp[i][j][4]=\max\left\{\begin{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}\right\}+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\n",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\n",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;
}

 

posted @ 2018-10-28 22:01  符拉迪沃斯托克  阅读(128)  评论(0编辑  收藏
Live2D