【Greater New York 2001】最大子矩阵
Description
已知矩阵的大小定义为矩阵中所有元素的和。给定一个矩阵,你的任务是找到最大的非空(大小至少是1 * 1)子矩阵。
比如,如下4 * 4的矩阵
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
的最大子矩阵是
9 2
-4 1
-1 8
这个子矩阵的大小是15。
Input
输入是一个N * N的矩阵。输入的第一行给出N (0 < N <= 100)。再后面的若干行中,依次(首先从左到右给出第一行的N个整数,再从左到右给出第二行的N个整数……)给出矩阵中的N2个整数,整数之间由空白字符分隔(空格或者空行)。已知矩阵中整数的范围都在[-127, 127]。
Output
输出最大子矩阵的大小。
Sample Input
4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
Sample Output
15
题目讲解
这道题实际上是最大子串和的改版,它是二维的而不是一维的。
这是动态规划的题目,按照习惯,作者先写了一个搜索代码。首先是输入,除了普通的将矩阵读入二维数组(S),这里作者从1开始,可以免去判断数组越界的操作,作者还在for循环中加入了横向的前缀和操作,便于横向的求和。接下来我们需要枚举求出最大值。从确定当前矩阵位置开始,作者选择用双重for循环枚举子矩阵的右下角。从这个右下角开始,我们在S[1~i][1~j]中寻找包含S[i][j]最大矩阵。用ans储存最大的一个矩阵的值。
接下来便是编写寻找矩阵的函数(CheckPoint),返回在搜索范围内以S[x][y]为右下角的最大矩阵值,参数表如下:
1.x:矩阵右下角第一维
2.y:矩阵右下角第二维
这两个参数描述了寻找范围。我们可以在函数内部定义一个int变量Check,储存最后寻找到的最大矩阵值。接下来需要枚举矩阵的左上角,范围在[1~x][1~y],同样是用双重for循环。确定了左上角和右下角便能确定该矩阵。然后就是求值,横向的值由于进行了前缀和操作,只需要用一个减法,即(i< j)[x][i]到[x][j]的和为S[x][j]-S[x][i-1]。竖向的只能再用一重for循环,我们用Point储存当前子矩阵的值。最后用Check储存Point的最大值。
返回主函数,输出。
大家也知道,这样一定会Time Limit Exceeded,所以我们还需要用动态规划。输入和搜索一模一样,但是范围的确定和前缀和改变了。我们需要用双重for循环分别枚举搜索范围的顶部和底部。与搜索同样,我们用int变量Check保存此时范围中最大子矩阵的值,此外,还需要定义now和last。由于前缀和方向改变,我们可以用减法求出竖向的和,接下来就要枚举矩阵的横向长度,仍然采用for循环。用Point储存竖向和,now来储存当前的最大矩阵值,last储存上一次枚举的最大矩阵值。Check每次循环后都需要储存最大值。单重循环退出后保存ans的最大值。
最后输出。
程序样例
猜得到要超时的 搜索
/*Lucky_Glass*/
#include<cstdio>
#include<algorithm>
using namespace std;
int n,S[105][105];
int CheckPoint(int x,int y)
{
int Check=-1e8;
for(int i=1;i<=x;i++)
for(int j=1;j<=y;j++)
{
int Point=0;
for(int m=i;m<=x;m++)
Point+=S[m][y]-S[m][j-1];
Check=max(Check,Point);
}
return Check;
}
int main()
{
int ans=-1e8;
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&S[i][j]),S[i][j]+=S[i][j-1];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
ans=max(CheckPoint(i,j),ans);
printf("%d\n",ans);
return 0;
}
构思巧妙的 动态规划
/*Lucky_Glass*/
#include<cstdio>
#include<algorithm>
using namespace std;
int n,S[105][105]={},ans=-1e8;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&S[i][j]),S[i][j]+=S[i-1][j];
for(int i=1;i<=n;i++)//列举上下端点
for(int j=i;j<=n;j++)
{
int Check=-1e8,last=0,now;
for(int k=1;k<=n;k++)//列举宽度
{
int Point=S[j][k]-S[i-1][k];
now=max(last+Point,Point);
Check=max(now,Check);
last=now;
}
ans=max(ans,Check);
}
printf("%d\n",ans);
return 0;
}