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
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; }