• 题解 [HNOI2019]序列


    题目传送门

    题目大意

    给出一个(n)个数的数列(A_{1,2,...,n}),求出一个单调不减的数列(B_{1,2,...,n}),使得(sum_{i=1}^{n}(A_i-B_i)^2)最小。

    (m)次查询,每次将某个(A_x)更改为(y),求出修改后的答案。查询之间互相独立。

    (n,mle 10^5)

    思路

    其实这道题正解是用保序回归,但是找规律也能找出来。

    我们通过观察发现,对于一段相同的(B_i)(B_i)是该段的平均值。于是我们大胆猜测,我们最优方案就是把(A)序列划分成一些区间,每一段的(B)都是(A)的平均值,并且(B)单调不降。

    我们发现这顺便还可以发现一个事情:我们肯定应该多分区间,否则的话我们肯定只分一段就完事了。

    于是,我们现在考虑一个区间对答案的贡献:

    [sum_{i=l}^{r} (A_i-d)^2 ]

    其中(d)是这段区间的平均值。

    [=sum_{i=l}^{r} (A_i^2-2A_id+d^2) ]

    [=sum_{i=l}^{r} A_i^2-dfrac{(sum_{i=l}^{r}A_i)^2}{r-l+1} ]

    于是我们发现我们只需要维护区间平方和、区间和、区间长度。于是,我们就顺利地拿到了(50)分。

    考虑(100)分。一个很显然的事情是,我们会改变的决策区间一定是([L,R]),至于([1,L))((R,n])都不会被影响,于是我们可以预处理一下。

    问题就是如何找到(L,R)。一个不是很显然的事情就是,我们选的端点一定都是一开始分的区间的某些端点。因为我们一个区间如果从中间分开,前一段的平均值一定比后一段的平均值大,因为如果比它小的话肯定分开更优(分得越多越优)。于是,如果我们不是选一开始的端点的话,被分开的那一段前一段一定比后一段的平均值大,与平均值单调不减矛盾,得证。

    同时,我们还发现答案是具有单调性的。于是我们可以考虑先二分(R),然后再二分(L)。二分(R)直接二分就好了,二分(L)的话,我们发现并不需要考虑前面的是否满足条件(这个自己想一下就明白了),只需要考虑分开的后面的,于是这个我们可以在主席树上查询。具体见代码。

    时间复杂度(Theta(nlog^2 n)),空间复杂度(Theta(nlog n))

    ( exttt{Code})

    #include <bits/stdc++.h>
    using namespace std;
    
    #define Int register int
    #define mod 998244353
    #define ll long long
    #define MAXN 100005
    
    template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
    template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
    template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
    
    int n,m,a[MAXN],inv[MAXN];
    int mul (int a,int b){return 1ll * a * b % mod;}
    int dec (int a,int b){return a >= b ? a - b : a + mod - b;}
    int add (int a,int b){return a + b >= mod ? a + b - mod : a + b;}
    
    struct node{
    	int rt,ans,top;//对应主席树的顶点、答案、栈顶编号 
    }pre[MAXN],suf[MAXN];
    
    struct Data{
    	int len,sqr;ll sum;//因为要比较平均值大小,所以区间和不能取模
    	Data(){}
    	Data(int len1,ll sum1,int sqr1){len = len1,sum = sum1,sqr = sqr1;}
    	Data operator + (Data p){return Data(len + p.len,sum + p.sum,add (sqr,p.sqr));}
    	Data operator - (Data p){return Data(len - p.len,sum - p.sum,dec (sqr,p.sqr));} 
    	bool operator < (Data p)const{return p.len ? (len ? 1.0 * sum / len < 1.0 * p.sum / p.len: 1) : 0;}
    	bool operator <= (Data &p)const{return p.len ? (len ? 1.0 * sum / len <= 1.0 * p.sum / p.len: 1) : 0;} 
    	int calc (){return dec (sqr,mul (mul (sum % mod,sum % mod),inv[len]));}
    }sum[MAXN],sta[MAXN];
    Data calc (int l,int r){return sum[r] - sum[l - 1];}
    
    struct Segment{
    	int cnt;
    	struct Node{
    		int son[2],l,r,k;//k是指l->r这写区间第一段区间的右端点 
    	}tree[MAXN * 60];
    	void Pushup (int x){
    		tree[x].l = tree[tree[x].son[0]].l,tree[x].r = tree[tree[x].son[tree[x].son[1] > 0]].r;
    		tree[x].k = tree[tree[x].son[0]].k;
    	}
    	void modify (int &x,int l,int r,int pos,int L,int R){
    		tree[++ cnt] = tree[x],x = cnt;
    		if (l == r) return tree[x].l = L,tree[x].r = tree[x].k = R,void ();
    		int mid = (l + r) >> 1;
    		if (pos <= mid) modify (tree[x].son[0],l,mid,pos,L,R);
    		else modify (tree[x].son[1],mid + 1,r,pos,L,R);
    		Pushup (x);
    	}
    	int queryr (int x,int l,int r,int pos){//查找第pos段区间的右端点 
    		if (l == r) return tree[x].r;
    		int mid = (l + r) >> 1;
    		if (pos <= mid) return queryr (tree[x].son[0],l,mid,pos);
    		else return queryr (tree[x].son[1],mid + 1,r,pos);  
    	}
    	int queryl (int x,int l,int r,int pos,Data &tmp){//找到最靠右的满足的L,并对答案进行合并 
    		if (r <= pos){
    			Data Lget = calc (tree[x].l,tree[x].k),Rget = calc (tree[x].k + 1,tree[x].r);
    			if (Rget + tmp <= Lget) return tmp = tmp + calc (tree[x].l,tree[x].r),0;
    			if (l == r) return tree[x].r;
    		}
    		int res,mid = (l + r) >> 1;
    		if (pos > mid && (res = queryl (tree[x].son[1],mid + 1,r,pos,tmp))) return res;
    		else return queryl (tree[x].son[0],l,mid,pos,tmp);
    	}
    }Tree;
    
    void Init(){
    	for (Int i = 1,top = 0;i <= n;++ i){
    		sta[++ top] = Data (1,a[i],mul (a[i],a[i]));
    		while (top > 1 && sta[top] <= sta[top - 1]) sta[top - 1] = sta[top - 1] + sta[top],-- top;
    		pre[i].ans = add (pre[i - sta[top].len].ans,sta[top].calc());//计算答案
    		pre[i].rt = pre[i - 1].rt,pre[i].top = top;
    		Tree.modify (pre[i].rt,1,n,top,i - sta[top].len + 1,i);
    	}
    	for (Int i = n,top = 0;i;-- i){
    		sta[++ top] = Data (1,a[i],mul (a[i],a[i]));
    		while (top > 1 && sta[top - 1] <= sta[top]) sta[top - 1] = sta[top - 1] + sta[top],-- top;
    		suf[i].ans = add (suf[i + sta[top].len].ans,sta[top].calc());
    		suf[i].rt = suf[i + 1].rt,suf[i].top = top;
    		Tree.modify (suf[i].rt,1,n,top,i,i + sta[top].len - 1);
    	}
    }
    
    signed main(){
    	read (n,m),inv[1] = 1;
    	for (Int i = 2;i <= n;++ i) inv[i] = mul (mod - (mod / i),inv[mod % i]);
    	for (Int i = 1;i <= n;++ i) read (a[i]),sum[i] = sum[i - 1] + Data (1,a[i],mul (a[i],a[i]));Init ();
    	write (pre[n].ans),putchar ('
    ');
    	for (Int i = 1,x,y;i <= m;++ i){
    		read (x,y);
    		int l = 0,r = suf[x + 1].top - 1;
    		while (l <= r){
    			int mid = (l + r) >> 1,Rpos = mid ? Tree.queryr (suf[x + 1].rt,1,n,suf[x + 1].top - mid + 1) : x;//Rpos就是选出来的R 
    			Data tmp = Data (1,y,mul (y,y)) + calc (x + 1,Rpos);int Lpos = x > 1 ? Tree.queryl (pre[x - 1].rt,1,n,pre[x - 1].top,tmp) : x; 
    			if (tmp < calc (Rpos + 1,Tree.queryr (suf[x + 1].rt,1,n,suf[x + 1].top - mid))) r = mid - 1;
    			else l = mid + 1;
    		}
    		int mid = r + 1,Rpos = mid ? Tree.queryr (suf[x + 1].rt,1,n,suf[x + 1].top - mid + 1) : x;
    		Data tmp = Data (1,y,mul (y,y)) + calc (x + 1,Rpos);int Lpos = x > 1 ? Tree.queryl (pre[x - 1].rt,1,n,pre[x - 1].top,tmp) : x; 
    		write (add (tmp.calc(),add (pre[Lpos].ans,suf[Rpos + 1].ans))),putchar ('
    ');
    	}
    	return 0;
    }
    
  • 相关阅读:
    ndoejs解析req,伪造http请求
    ndoejs创建多重文件夹
    路径path的正则通配符-nodejs
    例题1.5 快速排序
    例题1.3 整数划分问题
    sdcf day4 qaq模拟赛总结
    P1168 中位数
    浅谈LCA
    sdcf day1 qwq比赛题解
    2019山东夏令营划水记
  • 原文地址:https://www.cnblogs.com/Dark-Romance/p/13418541.html
Copyright © 2020-2023  润新知