6136. 【NOI2019模拟2019.4.19】最大子段和

有个长度为(2n-1)的序列,值域([-K,K])(整数),其中奇数位已经确定,偶数位可以任意钦定。

钦定值,最大化,最大子段和-最大非负段和。

(nle 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_kmax(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(nsqrt 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
");
		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
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/jz-597/p/14970077.html