【Luogu P1121】环状最大两段子段和

题目链接

题目描述

给出一段环状序列,即认为A[1]和A[N]是相邻的,选出其中连续不重叠且非空的两段使得这两段和最大。
( N 2 × 10 5 )

题解

我相信大多数人第一眼看到这道题的想法就是破环成链。
但是一看数据范围N到了 10^5 ,那么你枚举断点就已经有O(n)的复杂度了,转移想要做到log什么的不太可能。

不妨先来看没有环的,一种做法就是预处理从前向后与从后向前记录到达每一个点的最大子段,再枚举断点把两端接起来。
另一种方法就是dp了,我们设 d p [ 0 / 1 / 2 / 3 / 4 ] [ N ] 表示到N的时候 还没选/在选第一段/选完了第一段/在选第二段/选完了第二段 的最大和 转移即可。

其实链状做法的唯一不能出理的情况便是首位相接的,并且这算一段。
那么我们只需要强制选择首尾即可。
具体来说就是重新设 f [ 0 / 1 / 2 / 3 / 4 ] [ N ] , g [ 0 / 1 / 2 / 3 / 4 ] [ N ] 表示到N时的状态,但g是从后往前dp的,并且都强制选择最前面一段即可。

P.S. : 环状问题要么 破换成链 要么 分类讨论 。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<queue>
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
inline int read()
{
    int x=0;char ch=getchar();int t=1;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}
const int N=2e5+10;
int a[N];
int dp[4][N];
int f[5][N],g[5][N];
int n;
const int INF=2147483647;
int main()
{
    n=read();
    Set(dp,-127/3);Set(f,-127/3);Set(g,-127/3);
    register int sum=0;int ans=-INF;
    for(register int i=1;i<=n;++i) {
        a[i]=read();sum+=a[i];
        dp[0][i]=0;dp[1][i]=max(a[i],dp[1][i-1]+a[i]);
        dp[2][i]=max(dp[1][i-1],dp[2][i-1]);dp[3][i]=max(dp[2][i-1]+a[i],dp[3][i-1]+a[i]);
        ans=max(ans,dp[3][i]);
        f[1][i]=sum;f[2][i]=max(f[2][i-1],f[1][i-1]);f[3][i]=max(f[3][i-1],f[2][i-1])+a[i];
        f[4][i]=max(f[3][i-1],f[4][i-1]);
    }
    sum=0;
    for(register int i=n;i;--i)
    {
        sum+=a[i];
        g[1][i]=sum;g[2][i]=max(g[2][i+1],g[1][i+1]);g[3][i]=max(g[3][i+1],g[2][i+1])+a[i];
        g[4][i]=max(g[3][i+1],g[4][i+1]);
    }
    ans=max(ans,sum);
    for(register int i=1;i<n;++i)
    {
        register int mf=max(f[1][i],f[2][i]),mg=g[2][i];
        ans=max(ans,max(max(mf+mg,mf+g[4][i]),max(f[4][i]+g[1][i],f[3][i]+mg)));
    }
    printf("%d\n",ans);
}

猜你喜欢

转载自blog.csdn.net/element_hero/article/details/80461919