最大和子序列
这是一个非常原始的数学问题:给出一段序列,选出其中连续且非空的一段使得这段和最大。以下给出几个常用的算法
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 0 (1):0 (1+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;
}