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;
}
posted @ 2021-07-04 21:58  jz_597  阅读(154)  评论(0编辑  收藏  举报