6136. 【NOI2019模拟2019.4.19】最大子段和
有个长度为\(2n-1\)的序列,值域\([-K,K]\)(整数),其中奇数位已经确定,偶数位可以任意钦定。
钦定值,最大化,最大子段和-最大非负段和。
\(n\le 5000\)
吼题。
记最大子段和为\(A\),最大非负段和为\(A_0\)。
首先,最优解一定是:选择\(A\)对应区间\([L,R]\),区间内的空位填\(K\)或\(-1\),区间外的空位填\(-1\)。
证明:假设最优解中\(A\)的区间为\([L,R]\)。
考虑\(A_0\)的候选区间:
如果被\([L,R]\)包含,处理公共部分(用上面的策略)没有影响,处理非公共部分只对\(A\)有利。
如果不被包含,显然不可能真相交。此时:如果\(A_0\)大于\([L,R]\)中任意非负段,那么可以将任意非负段调到不超过\(A_0\)。如果有能调到\(A_0\)的,那么它成为了\(A_0\)的另一个候选区间,变成上面的情况;如果不能调到,它们一定要尽力调整,则每段中的空位顶到\(K\)。
其次,\(L=1+[a_1<0],R=2n-1-[a_{2n-1}<0]\)。
证明:假如最优解中\(A\)对应区间\([L_1,R_1]\),只考虑向左扩展。
如果将左边所有空位填\(K\),对\(A\)的贡献是\([L,L_1-1]\)的最大后缀和。又因为填到了\(K\),所以几乎所有后缀和其实都非负,因此最大后缀和是整个的和(如果\(L_1-1\)被固定而且为负数则其作为开头的后缀和为负,不过此时\(L_1-2\)肯定可以钦定为\(K\))。
考虑\(A_0\)的候选区间(不与\([L_1,R_1]\)相交的):\(A\)的增量一定大于等于调整后的它,自然大于等于调整后的它的增量。
然后就可以写出DP啦。设\(f_{i,j}\)表示到点\(i\)填了\(j\)个\(-1\),此时\(A_0\)最小值。写出来大概长成:\(f_{i,j}=\min_k\max(f_{k,j-1},g_{k,i-1})\),其中\(g_{l,r}\)表示\([l,r]\)中空位全填\(K\)此时最大非负区间和。
发现随着\(k\)增加,\(f_{k,j-1}\)增,\(g_{k,{i-1}}\)减。所以可以用个指针记个分界点,做到\(O(n^2)\)。
ls的神仙做法:
先考虑整个序列被固定的位置全部为\(K\),看看怎么填才可以使得答案最大。
枚举分成\(i\)段,每段肯定要尽量平均。然后列出\(i\)关于答案的式子,估计出最优答案大概在\(\sqrt{2n}\)处取到。
现在被固定的位置可以是其它数。猜个结论:\(i\)大概会比前面的情况更少一些。
于是在大概\(\sqrt{2n}\)范围内枚举\(i\),算下\(A_0\)最大值最小是多少,具体二分后贪心(扫过去空位填\(-1\)或\(K\)。如果填\(K\)非负区间和大于限制则填\(-1\)否则填\(K\)。然后得到真实的段数\(i'\),和\(i\)比较)。
时间\(O(n\sqrt n \lg nK)\)
using namespace std;
#include <bits/stdc++.h>
const int N=5005,INF=1e9;
int n,k;
int a[N];
int f[N][N],g[N][N];
int sum,ans;
int main(){
//freopen("in.txt","r",stdin);
//freopen("43.in","r",stdin);
freopen("max.in","r",stdin);
freopen("max.out","w",stdout);
scanf("%d%d",&n,&k);
for (int i=1;i<=n;++i){
scanf("%d",&a[i]);
sum+=a[i];
}
if (n==1){
printf("0\n");
return 0;
}
sum+=(n-1)*k-min(a[1],0)-min(a[n],0);
for (int i=1;i<=n;++i){
int s=0,mx=0;
for (int j=i;j<=n;++j){
if (a[j]<0)
s=0;
else{
s+=a[j];
mx=max(mx,s);
}
g[i][j]=mx;
s+=k;
mx=max(mx,s);
}
}
memset(f,127,sizeof f);
f[1][0]=0;
for (int i=2;i<=n;++i){
int k=i-1;
for (int j=i-1;j>=1;--j){
/*
int tmp0=INT_MAX;
for (int k=1;k<i;++k)
tmp0=min(tmp0,max(f[k][j-1],g[k][i-1]));
*/
if (j==1)
f[i][j]=max(f[1][0],g[1][i-1]);
else{
// k=max(k,j);
while (k>j && f[k-1][j-1]>g[k-1][i-1])
--k;
while (k<i && f[k][j-1]<=g[k][i-1])
++k;
int tmp=INT_MAX;
if (k<i) tmp=min(tmp,f[k][j-1]);
if (k-1>=j && f[k-1][j-1]<INF) tmp=min(tmp,g[k-1][i-1]);
f[i][j]=tmp;
}
}
}
for (int i=1;i<=n;++i)
for (int j=0;j<i;++j)
ans=max(ans,(sum-(k+1)*j)-max(f[i][j],g[i][n]));
printf("%d\n",ans);
return 0;
}