最大和子序列,最大和子矩阵--玩转最大和问题

最大和子序列

这是一个非常原始的数学问题:给出一段序列,选出其中连续且非空的一段使得这段和最大。以下给出几个常用的算法

1、穷举法

对序列中的所有子段进行遍历求和,通过比较不同子段的和,得到最大的子段和。

2、分治法

对于序列{a[i] | i = 1,2,3,…,n},取序列中心为分割点,将序列拆分为left:{a[i] | i = 1,2,…,n/2}和right:{a[i] | i = n/2+1,n/2+2,…,n}。这样最大的子序列将会有三种情况:(1)最大的子序列在left:{a[i] | i = 1,2,…,n/2}中;(2)最大的子序列在right:{a[i] | i = n/2+1,n/2+2,…,n}中;(3)最大的子序列一半在left,一半在right,也就是最大子序列中最少包含元素a[n/2]和a[n/2+1]。
对于这三种情况,只需要分别求解不同情况下的最大子段和,然后三者中最大的值既是整个序列的最大子段和。

3、动态规划

运用动态规划解决这个问题,关键的一步就是需要找到状态转移方程。对于序列{a[i] | i = 1,2,3,…,n},maxSum(m) = max{a[1]+a[2]+…+a[m]},即子字段{a[i] | i = 1,2,3,…,m}的最大字段和。由maxSum(m)到maxSum(m+1)的状态转移方程如下:
(1) 如果maxSum(m)>=0,maxSum(m+1)=maxSum(m)+a[m+1]
(2) 如果maxSum(m) < 0,maxSum(m+1)=a[m+1]
对于a[m+1]<0时,得到的maxSum(m+1) < maxSum(m)。所以,需要设置一个变量存储当前的最大字段值。上述的状态转移方程只是一个用于遍历不同子段值的方法。

穷举一般题都不能满足,分治写起来会比较麻烦,因此dp往往是最佳的选择。

DP模板

#define ll int
ll n, v[MAX], c[MAX], maxx = 0;

int main() {
	cin >> n;
	for (ll i = 0; i < n; i++)cin >> v[i];
	c[0] = v[0]; maxx = v[0];
	for (ll i = 1; i < n; i++) {
		if (c[i - 1] <= 0)c[i] = v[i];
		else c[i] = c[i - 1] + v[i];
		maxx = max(maxx, c[i]);
	}
	cout << maxx << endl;
}

最大和子矩阵

给定一个矩阵

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

求出和最大的子矩阵,看到这个问题的时候,我第一时间想到的是维护二维矩阵的前缀和,然后对每个点 ( x , y ) (x,y) (x,y)遍历他左上方的所有矩形的前缀和,相减即为该矩阵的和,取最大值即可


#define MAX 105
#define ll int

ll sum[MAX][MAX], a[MAX][MAX];

int main() {
	ll N;
	while (cin >> N) {
		memset(sum, 0, sizeof(sum));
		for (int i = 1; i <= N; i++) {
			for (int j = 1; j <= N; j++) {
				cin >> a[i][j]; sum[i][j] = sum[i][j - 1] + a[i][j];//按行求前缀数组
			}
		}
		//按行求前缀数组
		for (int j = 1; j <= N; j++)
			for (int i = 1; i <= N; i++)
				sum[i][j] = sum[i - 1][j] + sum[i][j];
		ll res = 0;
		for (int i = 0; i <= N; i++)
			for (int j = 0; j <= N; j++)
				for (int m = i; m <= N; m++)
					for (int n = j; n <= N; n++) {
						//如果端点在一行或者一列 那么直接前缀和之差
						if (m == i || n == j) res = max(res, sum[m][n] - sum[i][j]);
						//如果端点不同行也不同列
						else  res = max(res, sum[m][n] - sum[m][j] - sum[i][n] + sum[i][j]);
					}
		cout << res << endl;
	}
}

打眼一看复杂度 O ( n 4 ) O(n^4) O(n4),剪枝也减不了多少。,不出所料 O ( n 4 ) O(n^4) O(n4)应该被卡死,但是为了这个思路我还是写了

此时,我们应该想到,一维子序列的最大和数组就是在上述搜索的过程中优化得来的,那么同样的,二维应该如何进行优化呢?可不可以对每一列,我们把它与它右边的列分别相加进行组合,比如在考虑第一列的时候,我们考虑的次序分别为1,1+2,1+2+3,1+2+3+4,如果考虑第二列,那应该考虑的次序为2,2+3,2+3+4,将这些列的组合纵向相加,看例子

0 -2 -7 01):01+2):-2 。。。
9 2 -6 2 		 9			11
-4 1 -4 1       -4			-3
-1 8 0 -2       -1			 7

此时如果我们纵向求最大和子序列,对(1+2)而言,就是求(1,2)两列围成的区域内,最大和子矩阵,对(1+2)求最大和子序列,显然可以得到15,也就是正确结果。

#include<iostream>
#include<cmath>
#include<iomanip>
#include<string.h>
#include<vector>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;

#define MAX 505
#define inf 1e9
#define ll int
#define p pair<ll, ll>

ll n, a[MAX][MAX], b[MAX][MAX], res;


int main() {
	cin >> n;
	for (ll i = 1; i <= n; i++) for (ll j = 1; j <= n; j++) cin >> a[i][j];
	res = a[1][1];
	for (ll i = 1; i <= n; i++) {//从第一列开始
		memcpy(b, a, sizeof(a));
		for (ll j = i + 1; j <= n; j++) {//其后的第j列
			for (ll k = 1; k <= n; k++)
				b[k][j] += b[k][j - 1];
		}
		for (ll j = i; j <= n; j++) {//其后的第j列
			res = max(res, b[0][j]);
			for (ll k = 2; k <= n; k++) {
				if (b[k - 1][j] > 0) b[k][j] = b[k - 1][j] + b[k][j];
				res = max(res, b[k][j]);
			}
		}
	}
	cout << res << endl;
}
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值