题意:对数组a,要求找到两段不重合的子段,使得两子段和最大 / /每个子段至少有一个元素. T=30,n=5e4
一般的,联想到两种思路:
1.根据已知的o(n)求最大子段和的方式(hdu1003),推出此题的解法
2.定义一个新状态,用dp求解
因为是两段子段和,因此不好定义新的状态dp求解,故用第一种思路.
在hdu1003中,状态dp[i]是以a[i]为最尾端元素的最大子段和,同样的,我们可以从这个角度出发.
那么两个子段和与一个子段和如何转换?我们容易想到几种思路:
1.先找一个以a[i]结尾的最优子段和,然后在后面再找一个最优子段和(否定,后面那个有可能包含前面一个,不好处理)
2.找到总序列的最优子段,然后找到断点进行"切割"(否定,如1 -100 1 2 3就不能这样处理,而且找最小子段和的算法不好写)
3.找一个a[i]为结尾的最优子段和,再找一个以a[j]为起点的最优子段和(也可以理解为逆序数组找a[i]结尾,定义为dpp),二者相加(可行)
用方法3,可是先确定i,再确定j,找到最大的解,似乎要o(n^2)的复杂度来暴力,该如何解决?
其实可以用这样的思路:如果已经确定a[i],也就是确定了dp[i,]那么就是要从dpp[i+1]~dpp[n-1]找到最大的那个数,
其实,我们完全可以用数组r[i],记录dpp[i]~dpp[n-1]之中的最大值,而求出这个数组,会不会超时呢?
答案是不会的,因为求r时,可以运用递推的思想,
求r[i],dpp[i]~dpp[n-1]的最值,也就是求子问题:
dpp[i]和dpp[i+1]~dpp[n-1]的最值,得到状态转移方程r[i]=max(dpp[i],r[i+1]),问题就这么得到了解决.
一开始写的时候,我写了一个l数组,和r数组,然后遍历每个分隔点,根据l和r数组求解,这样要更费时间和空间一点:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn=500010;
const int inf=0x3f3f3f3f;
int t,n,cnt;
int a[maxn],dp[maxn],dpp[maxn],dp_[maxn],dpp_[maxn];
int main()
{
scanf("%d",&t);
while(t--){
memset(dp,0,sizeof(dp));//应该也不用全为0,首尾就可以了
memset(dpp,0,sizeof(dpp));
memset(dp_,0,sizeof(dp_));
memset(dpp_,0,sizeof(dpp_));
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&a[i]);
dp[0]=a[0],dpp[n-1]=a[n-1];
for(int i=1;i<n;i++){
if(dp[i-1]>0){
dp[i]=dp[i-1]+a[i];
}else {
dp[i]=a[i];
}
}
for(int i=n-2;i>=0;i--){
if(dpp[i+1]>0){
dpp[i]=dpp[i+1]+a[i];
}else {
dpp[i]=a[i];
}
}
dp_[0]=dp[0];
for(int i=1;i<n;i++)
dp_[i]=max(dp[i],dp_[i-1]);
dpp_[n-2]=dpp[n-1];
for(int i=n-3;i>=0;i--)
dpp_[i]=max(dpp_[i+1],dpp[i+1]);
int ans=dp_[0]+dpp_[0];
for(int i=1;i<n-1;i++){
ans=max(ans,dp_[i]+dpp_[i]);
}
printf("%d\n",ans);
}
return 0;
}
/*
1
7
2 2 3 -3 4 -4 5
2 4 7 4 8 4 9
9 7 5 2 5 1 5
2 4 7 7 8 8 9
7 5 5 5 5 5 5
1
6
2 -100 1 2 3 4
12
1
2
1 -10
-9
*/