写在前面:本题是聊天时从一位朋友那里所得,暂时没有找到题目出处与标解。博主自己用动态规划与一点小优化做出了本题,可能并不是最优解,欢迎指教。
题目描述
本题是最大连续子序列和的加强版:
最大连续子序列和问题_AryCra_07的博客-CSDN博客
给定一个数字序列
,求两段互不相交的连续子序列,使得两段子序列的和最大,输出这个最大和。
题目分析
两次DP
由于题目要求我们找两段互不相交的子序列,那必然一个在左,一个在右。那么,我们可以采用“two pointers”的思想(简单说来就是从两头遍历),化归成处理两个单段最大连续子序列的问题。
具体一点来说,基本思路如下:
- 处理单段子序列问题的时候,我们用dp记录以该点为结尾的最大连续子序列;
- 那么对于本题,我们可以定义两个dp数组dp_left,dp_right,从两头开始动态规划,dp_left记录左序列以该点结尾(从左到右数)时的最大子序列,dp_right记录右序列以该点结尾(从右往左数)的最大子序列;
- 完成之后,我们最后的操作是遍历每个点。对于每个点,我们要找到在它左边的最大子序列与在它右边的最大子序列(因为两个序列不能相交嘛,就以一个点为分界线来找),并求出基于这个点找到的最大左右序列之和,进而求出所有情况下的最大和。
一个优化
问题在与第3步的寻找操作。我们总不能每遍历到一个点,都去寻找一遍基于这个点的最大左序列和与最大右序列和。诚然这又可以用dp解决,但我们这里有一个很棒的优化方法。代码里见。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int dp_leftSequence[N], dp_rightSequence[N], sequence[N];
int main()
{
ios::sync_with_stdio(false); //关闭流同步,提高cin速度
int n;
cin >> n;
for(int i = 1; i <= n; i ++)
cin >> sequence[i];
dp_leftSequence[1] = sequence[1];
dp_rightSequence[n] = sequence[n]; //赋边界条件(初始状态)
for(int i = 1; i <= n; i ++)
dp_leftSequence[i] = max(sequence[i], dp_leftSequence[i - 1] + sequence[i]);
//状态转移方程
for(int i = 2; i <= n; i ++) //important optmization,简化寻找操作
if(dp_leftSequence[i] < dp_leftSequence[i - 1])
dp_leftSequence[i] = dp_leftSequence[i - 1];
for(int i = n; i >= 1; i --)
dp_rightSequence[i] = max(sequence[i], dp_rightSequence[i + 1] + sequence[i]);
//状态转移方程
for(int i = n - 1; i >= 1; i --) //important optmization,简化寻找操作
if(dp_rightSequence[i] < dp_rightSequence[i + 1])
dp_rightSequence[i] = dp_rightSequence[i + 1];
int tmp = 0, maxn = -1;
for(int i = 1; i <= n-1; i ++) { //遍历每个点
tmp = dp_leftSequence[i] + dp_rightSequence[i + 1];
maxn = max(tmp, maxn);
}
cout << maxn << '\n';
return 0;
}