zoukankan      html  css  js  c++  java
  • 【loj3059】【hnoi2019】序列

    题目

    给出一个长度为 (n) 的序列 (A) ;

    你需要构造一个新的序列(B) ,满足:

    • $B_{i} le B_{i+1} (1 le i lt n ) $
    • $sum_{i=1}^{n} (A_i - B_i)^2 $ 最小

    题解

    • 出题人和题解在这里 : http://15283746.blog.uoj.ac/blog/4966

    • 我只是整理了一下证明(Part 1)并套了一种做法(Part 2);

    • Part 1

    • 主要讨论最优的策略:

    • 引理一

      如果要求所有(B_i)相同等于(x),那么$x = frac{sum_{i=1}^{n} A_i }{n} $

      证明:写成关于(x)的二次函数即可证明;

    • 定理一

      ​ 答案一定是很多严格上升的等值段,即每一段的(B)相同,且等于这段(A)的平均值(v(A)) ;

      证明:由于相邻等值块的值严格不同,根据二次函数的性质,一定可以向靠近平均值的方向调整;

    • 引理二

      ​ 将任意一个序列延长之后,原序列对延长后的序列答案的贡献不小于原序列的最优答案;

      证明:由于是最优的所以显然,否则矛盾;

    • 引理三

      ​ 等值段的划分唯一;

      证明:

      ​ ( - _ -) 建立线性规划模型, $B_i le B_{i+1} $ 这个不等式组在空间中的解是一个凸集,最优的点相当于和圆((A_1,A_2,...,))的切点,感受一下可以知道是唯一的;

    • 定理二

      ​ 下列构造方法是最优的:
      ​ 维护一个以平均值为权值的单调栈,当加入一个点时,如果不满足单调性,则合并栈尾和当前区间,重复这个过程,最后将区间加入单调栈;

      证明:

      ​ 最优的划分为(H[x,y]) ,令如上策略的划分为(H'[x,y]) ,考虑归纳;

      ​ 假设对(1 le i lt n)均满足(H=H') ,当(i=n)时:

      ​ 1.如果(H')中含有两个及以上个等值块,可以划分两个长度<n的区间分别构造(H_1')(H_2')

      ​ 根据引理二,可以知道一定是最优的;

      ​ 由构造方式这两个区间一定可以直接拼接且$H' = H_1' + H_2' $;

      ​ 2.如果(H')中只有一个等值块,只需要证明任意大于1个等值块的(H)都不优即可;

      ​ 假设(i)是划分(H)的最后一个满足(i)(i+1)不同块的点;

      ​ 考虑(H'[1,i]),设(i)在的块是([j,i]),在(i+1)(n)之间一定存在第一个(k)合并了([ j,i ])([i+1,k])

      ​ 由于$v[j,i] lt v[i+1,n] , v[j,i] ge v[i+1,k] $ ;

      ​ 所以$v[k+1,n] gt v[j,i] ge v[i+1,k] $ ;

      ​ 所以(H'[1,k] + H'[k+1,n])一定合法且比(H)优;

    • 至此在不修改的时候可以 (O(n)) 解决问题;

    • Part 2

    • 考虑如何合并([1,i-1 ])([i,i])([i+1,n]),先讨论合并两个区间的情况;

    • 定理三

      ​ 两个区间的并的划分方案中任意一个划分点在子区间中也一定是划分点;

      从左到右执行,一定不会使左区间的划分点变多,从右往左同理,根据引理三得证,可以推广到多个区间的情况;

    • 根据引理三,设两个区间为([1,mid])([mid+1,n]),可以先处理出(H'[1,mid-1])(H'[mid+1,n]),根据定理三,为了方便修改意义区间元素直接是等值块,最终被合并的一定是左区间的一段后缀+右区间的一段前缀,令其为区间([L_0,R_0])(v[L_0,R_0]) 表示区间平均值;

    • 引理四

      ​ 当加入到右区间的$ R (时,设) L_0 $是此时合并到的左端点,则:

      1.合法条件为:$v[L_0,R] gt v_{L0-1} $ ,且具有可二分性;

      2.(L_0)是使得(v[L,R])最大的(L)

      证明:

      ​ 由于栈是单增的并且在合并过程中,加入点的权值的变化是单调不增的,所以12得证;

    • 定理四

      ​ 对于右边的一个等值块(R)(L)是其在引理四中的(L_0),若:
      1.(R<R_0) 则有(v[L,R] ge v_{R+1}) ;

      2.(R ge R_0) 则有(v[L,R] lt v_{R+1}) ;

      证明:

      ​ 由于(R_0)一定满足2,只需要证明对于一个满足的(R)(>R)的位置一定满足2即可;

      归纳假设(R)满足,左端点设为(L),对于(R+1),左端点设为(L'),有引理四2和假设得到:(v[L',R] le v[L,R] lt v_{R+1}),所以$v[L',R+1] lt v_{R+1} lt v_{R+2} $ ,得证;

    • 引理四和定理四在中间有一个修改点 (i) 的时候依旧适用;

    • 所以可以找出[1,i-1]的单调和[i+1,n]的单调栈(从n做到1再往后退栈),二分套二分即可;

    #include<bits/stdc++.h>
    #define ll long long 
    #define mod 998244353
    using namespace std;
    const int N=100010;
    int n,m,ny[N],pl,pr,len[N],ansl[N],ansr[N],pre[N],ql,qr,ans[N];
    struct query{
    	int x,y,id;
    	query(int _x=0,int _y=0,int _id=0):x(_x),y(_y),id(_id){};
    	bool operator <(const query&A)const{return x<A.x;}
    }Q[N];
    struct data{
    	ll x,y,z;
    	data(ll _x=0,ll _y=0,ll _z=0):x(_x),y(_y),z(_z){};
    	data operator +(const data&A)const{return data(x+A.x,y+A.y,z+A.z);}
    	data operator -(const data&A)const{return data(x-A.x,y-A.y,z-A.z);}
    	bool operator <=(const data&A)const{return y*A.x<=A.y*x;}
    	bool operator >=(const data&A)const{return y*A.x>=A.y*x;}
    }A[N],L[N],R[N],pR[N],sL[N],sR[N],psR[N],X,Y,Z;
    data getL(int l,int r){return sL[r]-sL[l-1];}
    data getR(int l,int r){return sR[r]-sR[l-1];}
    int cal(data now){
    	int x=now.x,y=now.y%mod,z=now.z%mod;
    	int v=(ll)y*ny[x]%mod;
    	return (z-(ll)v*y*2%mod+(ll)v*v%mod*x%mod+mod)%mod;
    }
    void lowerL(){
    	int l=1,r=pl+1;
    	while(l<r){
    		int mid=(l+r+1)>>1;
    		Z=Y+getL(mid,pl);
    		if(Z<=L[mid-1])r=mid-1;
    		else l=mid;
    	}
    	Z=Y+getL(ql=l,pl);
    }
    void lowerR(){
    	int l=1,r=pr+1;
    	while(l<r){
    		int mid=(l+r+1)>>1;
    		Y=X+getR(mid,pr);
    		lowerL();
    		if(Z>=R[mid-1])r=mid-1;
    		else l=mid;
    	}
    	Y=X+getR(qr=r,pr);
    	lowerL();
    }
    int main(){
    //	freopen("sequence.in","r",stdin);
    //	freopen("sequence.out","w",stdout);
    	scanf("%d%d",&n,&m);ny[1]=1;
    	for(int i=2;i<=n;++i){ny[i]=1ll*(mod-mod/i)*ny[mod%i]%mod;}
    	for(int i=1;i<=n;++i){
    		int x;scanf("%d",&x);
    		A[i]=data(1,x,(ll)x*x%mod);
    	}
    	for(int i=1;i<=m;++i){
    		int x,y;scanf("%d%d",&x,&y);
    		Q[i]=query(x,y,i);
    	}
    	sort(Q+1,Q+m+1);
    	L[0]=data(1,0,0);
    	R[0]=data(1,1e9+1,0);
    	for(int i=n;~i;--i){
    		data now=A[i];
    		while(now>=R[pr])now=now+R[pr--];
    		len[i]=++pr;
    		pR[i]=R[pr];R[pr]=now;
    		psR[i]=sR[pr];sR[pr]=sR[pr-1]+now;
    		pre[i]=ansr[pr];ansr[pr]=(ansr[pr-1]+cal(now))%mod;
    	}
    	ans[0]=ansr[pr];
    	for(int i=1,tl=0,tr=1;i<=m;++i){
    		while(tr<Q[i].x+1){
    			R[pr]=pR[tr];
    			sR[pr]=psR[tr];
    			ansr[pr]=pre[tr];
    			pr=len[++tr];
    		}
    		while(tl<Q[i].x-1){
    			data now=A[++tl];
    			while(now<=L[pl])now=L[pl--]+now;
    			L[++pl]=now;
    			sL[pl]=sL[pl-1]+now;
    			ansl[pl]=(ansl[pl-1]+cal(now))%mod;
    		}
    		X=data(1,Q[i].y,(ll)Q[i].y*Q[i].y%mod);
    		lowerR();
    		ans[ Q[i].id ] = ((ll)ansl[ql-1] + ansr[qr-1] + cal(Z)) %mod;
    	}
    	for(int i=0;i<=m;++i)printf("%d
    ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    jQuery轮播图(一)轮播实现并封装
    openSUSE 12.3 默认启动项
    最大堆(最小堆)
    二叉树基本操作续二:前序、中序、后序遍历(非递归 迭代方式)
    二叉树基本操作续一:二叉树建立、节点数统计
    二叉树基本操作:前序、中序、后序遍历(递归方式)
    Android如何打印std::cout/printf(重定向stdout)
    textarea高度跟随文字高度而变化
    箭头函数与普通函数的区别
    浏览器兼容问题
  • 原文地址:https://www.cnblogs.com/Paul-Guderian/p/10801584.html
Copyright © 2011-2022 走看看