序列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!!!注意初始化!!!

代码:

#include<bits/stdc++.h>
using 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<n;i++) ans1=max(ans1,flmax[i]+frmax[i+1]);
    for(ll i=2;i<n;i++) flmin[i]=min(flmin[i-1],(ll)0)+a[i];
    for(ll i=2;i<n;i++) flmin[i]=min(flmin[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<n-1;i++) ans2=min(ans2,flmin[i]+frmin[i+1]);
    ans2=sum-ans2;
    cout<<max(ans1,ans2);
    return 0;
}

压行真快乐

01-25 07:55