//压缩行,DP列,备忘录max
#include<iostream>
#include<cstring>
#include<cstdio>
#include <climits>
using namespace std;
int a[105][105],b[105][105];
int main()
{
//freopen("最大子矩阵和.txt","r",stdin);
int t,m,n,i,j,max;
cin>>t;
while(t--){
max=INT_MIN; //INT_MIN 表示整型的无穷小 包含在#include<climits> ,相应的也有INT_MAX
memset(b,0,sizeof(b));
cin>>m>>n;
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
cin>>a[i][j];
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
b[i][j]=b[i][j-1]+a[i][j];
for(i=1;i<=n;i++){ //子矩阵从第i列开始累加
for(j=i;j<=n;j++){ //子矩阵从第i列累加到第j列
//以下是求最大子串和(一维)
int num=0; //当前最大值
for(int k=1;k<=m;k++){ //k从第1行到第m行做一维的DP
if(num>0)
num+=(b[k][j]-b[k][i-1]); //要注意是DP(b[k][j]-b[k][i-1])(DP列 = 行变换,即 k 变换)
else
num=(b[k][j]-b[k][i-1]);
if(num>max)
max=num;
}
}
}
cout<<max<<endl;
}
return 0;
}
总结:状态压缩DP。这道题可以看做是一维最大子串和的二维扩展,一维最大子串和采用了备忘录似的DP,其采用的是用一个 b[i] 来记录:扫描到第 i 个元素时,其最优子串和的状态转移方程是:b[i] = max { a[i] , b[i] + a[i] } ,运用到这道题中,需要将二维的压缩成一维,然后进行如上算法。
举例:原始矩阵用 a[][] 表示
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2 ………………【1】
其拥有最大和的子矩阵为:
9 2
-4 1
-1 8
其和为15。
解题步骤:(1)压缩行;(2)DP列;(3)备忘录max
(1)压缩行:
将每行自左向右做累加,存入 b[i][j] 中。于是上述矩阵就变为:
0 -2 -9 -9
9 11 5 7
-4 -3 -7 -6
-1 7 7 5 ………………【2】
b[i][j] 表示第 i 行,自第 1 列累加到第 j 列的和。
如果想表示第 i 行,自第 j 列累加到第 k 列的和(j<=k),我们就可以用如下表达式:
b[i][k] - b[i][j-1] = 第 1 列累加到第 k 列的和 - 第 1 列累加到第 j-1 列的和
这样做的好处就是可以将求第 i 行的第 j 列累加到第 k 列这个过程的算法复杂度从O(n)压缩到O(1)。
(2)DP列:
得到矩阵【2】之后,按照一维DP的方式,对每列从第 1 行往第 M 行做DP。比如矩阵【2】中的第一列:0 9 -4 -1。
按照一维DP之后的b[]数组为:0 9 5 4,最大值为9,这就表明矩阵【1】中子矩阵
0
9
-4
-1
中的最大子矩阵和为9。
循环DP,共三层循环。
最外层 i 循环 1--N,表明子矩阵是从第 i 列开始累加的。
第二层 j 循环 i--N,表明子矩阵是从第 i 列累加到第 j 列。
第三层 k 从 1 到 M 做一维DP。
所以其复杂度为O(n^3)。如果穷举的话,需要确定子矩阵左上角坐标x,y,需要O(n^2);需要确定右下角坐标x,y,需要O(n^2);需要循环计算子矩阵和,O(n^2);一共是O(n^6)。
(3)备忘录max:
我们这种DP的解法是O(n^3)的时间复杂度,但是存储空间耗费不小,存 b[][],还要存做列DP之后的每行最优解。所以实际需要三维数组b 来存放。但是我们采用一个备忘录变量值sum,在每次DP后记录其值,反复比较保留最大的sum。最后留下的即为最大子矩阵和。
准备知识参看:最大子串和