序列dp
题目传送门
我们先不考虑环状,只考虑如何求出最大两段子段和
最大子段和转移方程:
f[i]表示以i为结尾的最大子段和 f[i]=max(f[i-1],0)+a[i]
ans[i]表示在前i项中的最大子段和 ans[i]=max(f[i],ans[i-1]);
最小子段和同理
想法1:暴力dfs,时间复杂度O(2^n),不T才怪
期望得分:0
想法2:斩环,先求一次最大子段和,将整个子段都置为-inf,再求一遍
hack数据:
6
-3 5 2 1 8 -9
正确:15 错误:13
期望得分:0~100(运气)
想法3(正解):因为我们求的是两段,所以必然能找到一个断点,使得左右两端分别的最大子段和是整个序列的最大两段子段和。进而想到正反都求一遍,枚举断点。
对于环,先观察("||||"为选取部分)
||||||||||||||—————||||||||||||||||||||||—————|||
这不就是把"最小两段子段和"抠掉嘛!!!!
特判:
||||||||||||||———||||||||||||||||||||||||||||||||—————,所以循环要从2到n-1
这也是两段最小子段和,但抠掉后只剩两段了,与没环没区别
想法4 听说有分段求的神仙想法,本蒟蒻不会,希望大佬讲解
注意开long long!!!注意初始化!!!
代码:
#includeusing namespace std; typedef long long ll; ll flmax[200005],frmax[200005]; ll flmin[200005],frmin[200005]; ll ans1=-1e15,ans2=1e15; ll a[200005]; ll sum; ll n; int main() { scanf("%lld",&n); flmax[0]=frmax[n+1]=-1e15; flmin[1]=frmin[n]=1e15; for(ll i=1;i<=n;i++) {scanf("%lld",&a[i]); sum+=a[i];} for(ll i=1;i<=n;i++) flmax[i]=max(flmax[i-1],(ll)0)+a[i]; for(ll i=1;i<=n;i++) flmax[i]=max(flmax[i],flmax[i-1]); for(ll i=n;i>=1;i--) frmax[i]=max(frmax[i+1],(ll)0)+a[i]; for(ll i=n;i>=1;i--) frmax[i]=max(frmax[i],frmax[i+1]); for(ll i=1;i 1]); for(ll i=2;i 1],(ll)0)+a[i]; for(ll i=2;i 1],flmin[i]); for(ll i=n-1;i>1;i--) frmin[i]=min(frmin[i+1],(ll)0)+a[i]; for(ll i=n-1;i>1;i--) frmin[i]=min(frmin[i+1],frmin[i]); for(ll i=2;i 1;i++) ans2=min(ans2,flmin[i]+frmin[i+1]); ans2=sum-ans2; cout<<max(ans1,ans2); return 0; }
压行真快乐