- 給你一個首尾相接的數組 (即環),選出其中連續 不重疊 且 非空 的 兩段 使得這兩段和最大。
- 。
- 注意兩段子段可以相連(相當於一段)。
對於環上的 dp
,我們一般考慮破環爲鏈法,即把環轉化爲鏈。
破環爲鏈法中,又有幾種比較常見的處理方法,這裏列舉兩種:
- 把數組複製兩遍,即把數組 變成數組 ,其中 。這是最常用的一種破環爲鏈法。
- 不復制,直接用其它方法把環轉化爲鏈。
在本題中,我們發現雖然第一種很常用,但是我們仍然無法用它來解決問題。所以我們考慮第二種方法。
我們發現用第二種方法時,我們無法把所有的情況統一在一起討論,這個時候,我們就需要用到另一種數學( 也適用)的方法了,那就是:分類討論。
-
第一種情況:兩段子段都不跨越首尾,即類似於這樣的:
表示該位不選, 表示選且屬於第一子段, 表示選且屬於第二子段。
我們記 表示 的最大子段和, 表示 的最大子段和,則答案爲:
-
第二種情況:其中一個子段跨越了首尾,即類似於此:
命名方法同上。
直接求很難,我們就考慮反着做,畢竟正難則反。
我們發現 所佔據的正好就是兩個子段,於是,我們即 表示 的最小子段和, 表示 的最小子段和。則答案爲:
注意當 正好就是整個區間的情況,需要特判,因爲這樣相當於選得數爲空。
const int N=2e5+100;
#define ll long long
int n;ll ans,res,sum;
ll f[N],g[N],h[N],a[N];
const ll inf=0x3f3f3f3f3f;
inline ll calc_min_sum(){
h[0]=h[n+1]=f[0]=g[n+1]=inf;
for(register int i=1;i<=n;i++){
h[i]=min(h[i-1]+a[i],a[i]);
f[i]=min(f[i-1],h[i]);
}
for(register int i=n;i>=1;i--){
h[i]=min(h[i+1]+a[i],a[i]);
g[i]=min(g[i+1],h[i]);
}
register ll ret=inf;
for(int i=1;i<=n;i++)
ret=min(ret,f[i]+g[i+1]);
return ret;
}
inline ll calc_max_sum(){
h[0]=h[n+1]=f[0]=g[n+1]=-inf;
for(register int i=1;i<=n;i++){
h[i]=max(h[i-1]+a[i],a[i]);
f[i]=max(f[i-1],h[i]);
}
for(register int i=n;i>=1;i--){
h[i]=max(h[i+1]+a[i],a[i]);
g[i]=max(g[i+1],h[i]);
}
register ll ret=-inf;
for(int i=1;i<=n;i++)
ret=max(ret,f[i]+g[i+1]);
return ret;
}
int main(){
freopen("t1.in","r",stdin);
n=read();//我們的程序開始了
for(int i=1;i<=n;i++)
sum+=(a[i]=read());
ans=calc_max_sum();//求最大
res=calc_min_sum();//求最小
if (res==sum){//全部數子爲負
res=ans=-inf;//最大;次大
for(int i=1;i<=n;i++){
if (a[i]>res){
ans=res;
res=a[i];
}
else ans=max(ans,a[i]);
}
printf("%lld",ans+res);
}
else{//數字有正有負(普通情況)
printf("%lld",max(ans,sum-res));
}
return 0;
}