DP
1. 最大的和
来源《信息学奥赛一本通》
原题链接
题目描述
对于给定的整数序列 $A=\lbrace a_1,a_2,…,a_n \rbrace$,找出两个不重合连续子段,使得两子段中所有数字的和最大。
我们如下定义函数 $d(A)$:
$$d(A) = max_{1≤s_1≤t_1<s_2≤t_2≤n} \lbrace \sum^{t_1}_{i=s_1}a_i+\sum^{t_2}_{j=s_2}a_j \rbrace$$
我们的目标就是求出 $d(A)$。
输入格式
第一行是一个整数 $T$,代表一共有多少组数据。
接下来是 $T$ 组数据。
每组数据的第一行是一个整数,代表数据个数据 $n$,第二行是 $n$ 个整数 $a_1,a_2,…,a_n$。
输出格式
每组数据输出一个整数,占一行,就是 $d(A)$ 的值。
数据范围
$1 \le T \le 30$,
$2 \le n \le 50000$,
$|a_i| \le 10000$
输入样例
10
1 -1 2 2 3 -3 4 -4 5 -5
输出样例
13
算法:(线性dp + 前后缀分解)\(O(n)\)
算法内容
-
首先我们先回忆最大连续子序列和问题是如何解决的:
状态表示:
\(\qquad\)集合:\(f[i]\)表示以\(1\)~\(i\)中以\(i\)结尾的所有连续子序列
\(\qquad\)属性:最大值
状态计算:
\(\qquad\)集合划分:分为两类:第一类是只包含\(i\),第二类是不只包含\(i\)
\(\qquad\)转移方程:\(f[i] = max(f[i - 1] + w[i], w[i])\),即 \(f[i] = max(f[i - 1], 0) + w[i]\),优化空间后为\(s = max(s, 0) + w[i]\) -
前后缀分解技巧:
本题可采取用\(i\)来枚举两段区间的分界线,预处理\(g[i]\)为以i左侧的最大连续子序列和,\(h[i]\)为i右侧的最大连续子序列和,此时以\(i\)为分界线的满足题意的答案就是\(res = g[i] + h[i + 1]\),我们在遍历所有分界线时只需要对\(res\)取\(max\)即可得到最终答案
C++代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 50010, INF = 1e9;
int n;
int w[N], g[N], h[N];
int main()
{
int T;
scanf("%d", &T);
while (T -- )
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
g[0] = -INF;
for (int i = 1, s = -INF; i <= n; i ++ )
{
s = max(s, 0) + w[i];
g[i] = max(g[i - 1], s);
}
h[n + 1] = -INF;
for (int i = n, s = -INF; i; i -- )
{
s = max(s, 0) + w[i];
h[i] = max(h[i + 1], s);
}
int res = -INF;
for (int i = 1; i <= n; i ++ )
res = max(res, g[i] + h[i + 1]);
printf("%d\n", res);
}
return 0;
}