zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    Codeforces 231E
    Practice 15.07.07 计算几何
    Codeforces 552E
    Topcoder SRM 661 (Div.1) 250 MissingLCM
    HDU 4971
    Codeforces Round #306 (Div. 2)
    URAL 1988
    URAL 2032
    ServiceStack.Ormlit 事务
    ServiceStack.Ormlit 使用Insert的时候自增列不会被赋值
  • 原文地址:https://www.cnblogs.com/jz-597/p/14970077.html
Copyright © 2011-2022 走看看