最大子矩阵(天坑)

119 篇文章 4 订阅
18 篇文章 0 订阅

前言:我真的不知道,在不知道最大子矩阵解法的情况下,我是怎么撑到现在的,我可以回去学习文化课了


题目链接

问题描述:

求一个M*N的矩阵的最大子矩阵和。
比如在如下这个矩阵中:
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2 
拥有最大和的子矩阵为:
9 2
-4 1
-1 8
其和为15


分析:

这是一个经典问题了,我们最先想到的(最耿直的)做法就是枚举矩阵的左上角和右下角

复杂度应该是n^6的,即使加上二维前缀和优化,复杂度也是高达n^4


那我们来考虑一下,子矩阵都有哪些情况,

因为原矩阵有M行,那么ta的子矩阵就有可能有1~M行,每一种的首行有可能是1~M-k+1中的任何一种(k为子矩阵的宽)

假如说k=2,样例中的矩阵就可以分成以下三种情况:

0 -2 -7 0          9 2 -6 2              -4 1 -4 1
9 2 -6 2          -4 1 -4 1              -1 8 0 -2 


在每一种情况里,我们还要找出一个最大的子矩阵,当然,这只是局部最大,不一定是问题的解

但是,如果我们知道每一种情况的最大矩阵,要找出问题的最终解就比较简单了


首先,本着化繁为简的原则,我们先考虑最简单的方法,k=1(实际上就是一个序列)

假设我们的面前摆着一个序列

9 2 -6 2

我们需要求出ta的最大子段和,我们可以用dp解决

(这里不得不提一句,求解不定区间的最大子段和,使用的是线段树

dp方程:f[i]=max(f[i-1]+a[i],a[i]) ,f[i]表示1到i的最大子段和(这个子段必须包括i

(考虑当前元素,ta可以加入i-1,也可以单独作为一个序列)

那么上面那一个行得到的f数组就是:9,11,5,7(max=11)


这样我们就得到了k=1时,这种情况下的答案

但是我们不仅要解决k=1,我们面临的问题是k=2,3,4,...,M

那怎么办呢


注意,我们重新审视我们的前提之后,会发现我们枚举了k,也就是说,我们已经知道了子矩阵的宽度

那如果我们在这个条件下,选择了x1到x2(列数)的矩阵,那么这一列上的元素我们都要计算进去


可能这样解释的云里雾里,看个例子:

5 1 2 5 -9

-1 2 4 1 5

4 1 6 4 -4

假设我们现在要求的是

-1 2 4 1 5

4 1 6 4 -4

中的最大子矩阵,我们的核心思想不外乎 枚举

我们枚举了左右端点:x1=2,x2=4

这样我们的矩阵和就是(2+1)+(4+6)+(1+4)

我为什么要加括号? 为了说明无论枚举的左右端点是什么,同一列上的元素一定是同时行动的


那这就提示我们,既然一起加减,为什么我们不能把同一列上的元素都捆绑到一起,一同考虑呢?

很好,这个思路指引着我们找到了一个美妙的优化方式

如果k=2,我们就把两行的元素上下对应相加,最后得到一个序列,在序列上求最大子段和即可


我们现在的算法变成了这样:

枚举上下界限(n^2),得到这些行中每一列的权值和,组成一个新序列,在这个序列中运行dp得到最大子段和


但是计算每一列的权值和还是需要浪费时间,我们要是能够O(1)得出新序列,整个算法就可以降至O(n^3)

这个问题很容易解决,我们只要利用前缀和的思想,维护一个列上前缀和即可


tip

dp方程不要写错了,不要慌


#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int INF=1e9;
int n;
int a[103][103],f[103];

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	    for (int j=1;j<=n;j++)
	    {
	    	int x;
	    	scanf("%d",&x);
	    	a[i][j]=a[i-1][j]+x;             //a[i][j]前i行,第j列的前缀和 
		}

    int ans=-INF;
	for (int i=1;i<=n;i++)                   //枚举上下边界 
	    for (int j=i;j<=n;j++)
		{
			memset(f,0,sizeof(f));
			for (int k=1;k<=n;k++)
			{
				f[k]=max(a[j][k]-a[i-1][k],f[k-1]+a[j][k]-a[i-1][k]);
				ans=max(ans,f[k]);
			}
		} 
	
	printf("%d",ans);
	return 0;
} 



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值