最大字段和+二维最大矩阵和

本文介绍了从一维最大子段和问题延伸至二维最大矩阵和问题的解决策略,通过动态规划和前缀和优化算法复杂度,实现高效求解。并提供了具体代码实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是两道题,不过对于从一维走向二维的思想启发很有帮助,可以借鉴。

最大子段和

题意:

一个数列,从中抽一串连续数,数字和最大。(数据包含负数)

输入:

输入数字个数n,然后输入一列数。

(草率的说明)

暴力做法三次方,枚举左右端点然后加起来。

用前缀和可以优化成平方,枚举左右端点。

但数据范围是1e6 qwqwq

我推荐O(n)做法:动态规划(此处约等于贪心)

我们设f[i]表示结尾是i号数字的最大字段和。

那除了第一个,对于任何一个f[i],它要么加上前面的最大字段和,要么自己独立成段。

那判断条件是什么呢?f[i-1]>0。

如果前面的最大字段和大于0,那你当然可以把当前数加上前面的字段。但如果前面是0或者负数,那不加自然最好。

所以说f[i]=(f[i-1]>0)?f[i-1]+a[i]:a[i]。

最后记得sort一遍搞出最大的那个。

记得初始化第一个哦。

附代码。

​
#include<bits/stdc++.h>
#define in read()
using namespace std;
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();
		if(ch=='-')
		f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}
	return cnt*f;
}
int n;
long long f[200003];
int a[200003];
int main(){
	n=in;
	for(int i=1;i<=n;i++){
		a[i]=in;
	}
	f[1]=a[1];
	for(int i=2;i<=n;i++){
		if(f[i-1]>=0)f[i]=(long long)a[i]+f[i-1];
		else f[i]=(long long)a[i];
	}
	
	sort(f+1,f+n+1);cout<<f[n];
	return 0;
}

​

第一个记得初始化哦。

下面进入二维。

二维最大矩阵

题意:给一个数字矩阵,求最大的一个子矩阵。

输入:n排,m列,n*m个数。

输出:当然是最大的和啦。

可以看出朴素枚举算法是四次方。那太可怕了,数据范围是500。

这里给读者一个提示:

想想一维如何变成二维,如何在一维的基础上增加一个可枚举的维度。

 

 

这里继续。

矩阵最大的特点是,你不能一个一个从头开始推,因为你不确定之前的最大子矩阵的图形样子。

既然如此,我们就从一维扩张入手。

一维无论如何也只能搞完一排

但如果,这一排比较“胖”,胖成了一个矩阵呢?

一个矩阵,我们假设它占了k排,但宽不知道。

那我们就把这k排每一列的k个数字加起来当成一个数,将这个k*m的矩阵变成一个m长度的数列。这个数列你用一个前缀和就可以轻松解决。

现在就直接套最长字段就可以啦!

可以看出,我们在一维的基础上,暴力枚举这k排的位置以及k的大小,然后对于每个情况进行一次最长字段,打擂台取max,得到的就是答案啦。

复杂度n³(枚举开头点,结尾点,计算字段)

上代码。

#include<bits/stdc++.h>
#define in read()
using namespace std;
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();
		if(ch=='-')
		f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}
	return cnt*f;
}
int n,m;
long long a[502][502];
long long f[502];
long long b[502];
long long sum[502][502];
long long go(int i1,int i2){
	b[1]=sum[1][i2]-sum[1][i1-1];
	f[1]=(b[1]>0)?b[1]:0;
	for(int i=2;i<=n;i++){
		b[i]=sum[i][i2]-sum[i][i1-1];
		f[i]=b[i];
		if(f[i-1]>0)f[i]+=f[i-1];
	}
	sort(f+1,f+n+1);
	return f[n];
}
long long maxx=-0x7fffffffffffffff;
int main(){
	n=in;m=in;
	long long cnt=-0x7fffffffffffff,flag=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%lld",&a[i][j]);
			if(a[i][j]>0)flag=1;
			cnt=max(cnt,a[i][j]);
			sum[i][j]=a[i][j]+sum[i][j-1];
		}
	}
	if(!flag){cout<<cnt;return 0;}//这是一步特判,小心如果全是负数,容易卡出问题。
	for(int i1=1;i1<=m;i1++){
		for(int i2=i1;i2<=m;i2++){//枚举开排点和结排点
			long long now=0;
			for(int k=1;k<=n;k++){//这个k没有上文的意思
				now+=sum[k][i2]-sum[k][i1-1];
				if(now<0)now=0;
				maxx=max(maxx,now);
//更简单的最大子段和求法,可以让此处复杂度从2n变成n,否则要T6个点。
			}
		}
	}
	cout<<maxx;
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值