竞赛题目讲解-【Greater New York 2001】最大子矩阵

【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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值