• 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;
    }
    
  • 相关阅读:
    leetcode 54. 螺旋矩阵 js高效实现
    leetcode 141. 环形链表 js 实现
    通过 BFC 实现页面布局
    leetcode 1351. 统计有序矩阵中的负数 js实现
    js 实现元素拖拽
    如何在 chrome控制台快速获取某段 html?
    leetcode 160. 相交链表 js 实现
    leetcode 70. 爬楼梯 js实现
    nodejs中事件循环中的执行顺序
    leetcode 300. 最长递增子序列 js 动态规划实现
  • 原文地址:https://www.cnblogs.com/jz-597/p/14970077.html
Copyright © 2020-2023  润新知