线段树维护方差和区间开平方

线段树应用

线段树维护方差(线段树维护区间平方和)

例题:lgP1471 方差

思路引领

首先,我们来推一下方差的式子:

\[\dfrac{1}{n} \sum\limits_{i=1}^n(a_i-\overline{a})^2 \]

\[=\dfrac{{(a_1-\overline{a})}^2+{(a_2-\overline{a})}^2+{(a_3-\overline{a})}^2+……+{(a_n-\overline{a})}^2}{n}\ \]

\[=\dfrac{a_1^2-2 \times a_1 \times \overline{a}+\overline{a}^2+a_2^2-2 \times a_2 \times \overline{a}+\overline{a}^2+a_3^2-2 \times a_3 \times \overline{a}+\overline{a}^2+……a_n^2-2 \times a_n \times \overline{a}+\overline{a}^2}{n} \]

\[=\dfrac{a_1^2-2 \times a_1 \times \overline{a}+a_2^2-2 \times a_2 \times \overline{a}+a_3^2-2 \times a_3 \times \overline{a}+……+a_n^2-2 \times a_n \times \overline{a}+\overline{a}^2 \times n}{n} \]

\[=\overline{a}^2+\dfrac{a_1^2-2 \times a_1 \times \overline{a}+a_2^2-2 \times a_2 \times \overline{a}+a_3^2-2 \times a_3 \times \overline{a}+……+a_n^2-2 \times a_n \times \overline{a}}{n} \]

\[=\overline{a}^2+\dfrac{(a_1^2+a_2^2+a_3^2+……+a_n^2)-2 \times \overline{a} \times (a_1+a_2+a_3+……+a_n)}{n} \]

\[=\overline{a}^2+\dfrac{a_1^2+a_2^2+a_3^2+……+a_n^2}{n}-\dfrac{2 \times \overline{a} \times (a_1+a_2+a_3+……+a_n)}{n} \]

\[=\overline{a}^2+\dfrac{a_1^2+a_2^2+a_3^2+……+a_n^2}{n}-2 \times \overline{a} \times \dfrac{a_1+a_2+a_3+……+a_n}{n} \]

\[=\overline{a}^2+\dfrac{a_1^2+a_2^2+a_3^2+……+a_n^2}{n}-2\times \overline{a}^2 \]

\[=-\overline{a}^2+\dfrac{a_1^2+a_2^2+a_3^2+……+a_n^2}{n} \]

所以我们只需要维护区间和和区间平方和即可。

\(\textbf{当我们拿到一个线段数的题的时候,我们要分析题目需要维护什么。}\)

维护区间和不用说什么,线段树的正常操作。

考虑如何维护区间平方和。

维护操作

重点就在于懒标的操作。

我们可以这样理解:

我们可以设区间加的数为 \(\vartriangle x\),我们原来的数为 \(a_i^2\)

那么每个节点存储的平方和为 \(a_l^2+a_{l+1}^2+……+a_r^2\)

变化之后为

\[(a_l+\vartriangle x)^2+(a_{l+1}+\vartriangle x)^2+……+(a_r+\vartriangle x)^2 \]

\[=a_l^2+2\times a_l \times \vartriangle x+\vartriangle x^2+a_{l+1}^2+2 \times a_{l+1} \times \vartriangle x +\vartriangle x^2+……+a_{r}^2+2 \times a_{r} \times \vartriangle x +\vartriangle x^2 \]

\[=a_l^2+a_{l+1}^2+……+a_r^2+2 \times \vartriangle x \times (a_l+a_{l+1}+……a_r)+(r-l+1) \times \vartriangle x^2 \]

其中增加的量就是 \(2 \times \vartriangle x \times (a_l+a_{l+1}+……a_r)+(r-l+1) \times \vartriangle x^2\)

根据上面的式子,我们可以写出懒标的下传代码:

void push_down(int now)
{
	if(!tree[now].tag)  return ;
	tree[lc].pows+=tree[now].tag*tree[now].tag*tree[lc].len+2*tree[lc].sum*tree[now].tag;
	tree[rc].pows+=tree[now].tag*tree[now].tag*tree[rc].len+2*tree[rc].sum*tree[now].tag;
	tree[lc].sum+=tree[now].tag*tree[lc].len;
	tree[rc].sum+=tree[now].tag*tree[rc].len;
	tree[lc].tag+=tree[now].tag;
	tree[rc].tag+=tree[now].tag;
	tree[now].tag=0;
}

这里要注意一点,我们的区间和和区间平方和是一起下传懒标的,因为区间平方和的更新需要当前的区间和,所以要先更新区间平方和,后更新区间和。

\(code\)

/*
	线段树
	date:2022.8.12
	worked by respect_lowsmile 
*/
#include<iostream>
#define lc now<<1
#define rc now<<1|1
using namespace std;
const int N=1e5+5;
int n,m;
struct node
{
	double sum,tag,len,pows;
};
node tree[N<<2];
void push_up(int now)
{
	tree[now].sum=tree[lc].sum+tree[rc].sum;
	tree[now].pows=tree[lc].pows+tree[rc].pows;
}
void push_down(int now)
{
	if(!tree[now].tag)  return ;
	tree[lc].pows+=tree[now].tag*tree[now].tag*tree[lc].len+2*tree[lc].sum*tree[now].tag;
	tree[rc].pows+=tree[now].tag*tree[now].tag*tree[rc].len+2*tree[rc].sum*tree[now].tag;
	tree[lc].sum+=tree[now].tag*tree[lc].len;
	tree[rc].sum+=tree[now].tag*tree[rc].len;
	tree[lc].tag+=tree[now].tag;
	tree[rc].tag+=tree[now].tag;
	tree[now].tag=0;
}
void build(int now,int l,int r)
{
	tree[now].len=r-l+1;
	if(l==r)
	{
		scanf("%lf",&tree[now].sum);
		tree[now].pows=tree[now].sum*tree[now].sum;
		return ;
	}
	int mid=(l+r)>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
	push_up(now);
}
void update(int now,int l,int r,int L,int R,double v)
{
	if(l>=L&&r<=R)
	{
		tree[now].tag+=v;
		tree[now].pows+=v*v*tree[now].len+2*v*tree[now].sum;
		tree[now].sum+=tree[now].len*v;
		return ;
	}
	push_down(now);
	int mid=(l+r)>>1;
	if(L<=mid)  update(lc,l,mid,L,R,v);
	if(R>mid)   update(rc,mid+1,r,L,R,v);
	push_up(now);
}
double query_p(int now,int l,int r,int L,int R)
{
	double res=0;
	if(l>=L&&r<=R)
		return tree[now].sum;
	push_down(now);
	int mid=(l+r)>>1;
	if(L<=mid)  res+=query_p(lc,l,mid,L,R);
	if(R>mid)   res+=query_p(rc,mid+1,r,L,R);
	return res;
}
double query_f(int now,int l,int r,int L,int R)
{
	double res=0;
	if(l>=L&&r<=R)
		return tree[now].pows;
	push_down(now);
	int mid=(l+r)>>1;
	if(L<=mid)  res+=query_f(lc,l,mid,L,R);
	if(R>mid)   res+=query_f(rc,mid+1,r,L,R);
	return res;
}
int main()
{
	scanf("%d %d",&n,&m);
	build(1,1,n);
	for(int i=1;i<=m;++i)
	{
		int op,x,y;
		double v;
		scanf("%d %d %d",&op,&x,&y);
		if(op==1)
		{
			scanf("%lf",&v);
			update(1,1,n,x,y,v);
		}
		if(op==2)	printf("%.4lf\n",query_p(1,1,n,x,y)/(y-x+1));
		if(op==3)
		{
			double p=query_p(1,1,n,x,y)/(y-x+1);
			printf("%.4lf\n",-p*p+query_f(1,1,n,x,y)/(y-x+1));
		}
	}
	return 0;
}

线段数维护开平方

例题:lgP4145 上帝造题的七分钟 2 / 花神游历各国

思路引领

很难办的一点是开平方并不满足区间传递性。

也就是说,\(\sqrt a+\sqrt b \ne \sqrt{a+b}\)

那我们怎么办???暴力单点改???

那肯定TLE。

其实我们可以发现一个很有趣的性质,那就是如果一个数是 \(1\),那么它无论进行多少次开平方操作,它的值依旧是 \(1\)

也就是说,如果一个位置的值是 \(1\),我们完全没有修改的必要。

我们可以维护一个区间最大值,如果这个区间的最大值都是 \(1\) 了,说明这个区间全部都是 \(1\),我们就没有必要去修改了。

因为开平方操作,数量级还是降得很快的。

\(code\)

/*
	线段树
	date:2022.8.12
	worked by respect_lowsmile 
*/
#include<iostream>
#include<cmath>
#define lc now<<1
#define rc now<<1|1
#define int long long
using namespace std;
const int N=1e5+5;
struct node
{
	int sum,maxc;
};
node tree[N<<2];
int n,m;
int Max(int a,int b)
{
	return a>b?a:b;
}
void push_up(int now)
{
	tree[now].sum=tree[lc].sum+tree[rc].sum;
	tree[now].maxc=Max(tree[lc].maxc,tree[rc].maxc);
}
void build(int now,int l,int r)
{
	if(l==r)
	{
		scanf("%lld",&tree[now].sum);
		tree[now].maxc=tree[now].sum;
		return ;
	}
	int mid=(l+r)>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
	push_up(now);
}
void update(int now,int l,int r,int L,int R)
{
	if(tree[now].maxc==1)  return ;
	if(l==r)
	{
		tree[now].sum=sqrt(tree[now].sum);
		tree[now].maxc=tree[now].sum;
		return ;
	}
	int mid=(l+r)>>1;
	if(L<=mid)	update(lc,l,mid,L,R);
	if(R>mid)   update(rc,mid+1,r,L,R);
	push_up(now);
}
int query(int now,int l,int r,int L,int R)
{
	int res=0;
	if(l>=L&&r<=R)
		return tree[now].sum;
	int mid=(l+r)>>1;
	if(L<=mid)  res+=query(lc,l,mid,L,R);
	if(R>mid)   res+=query(rc,mid+1,r,L,R);
	return res;
}
signed main()
{
	scanf("%lld",&n);
	build(1,1,n);
	scanf("%lld",&m);
	for(int i=1;i<=m;++i)
	{
		int op,l,r;
		scanf("%lld %lld %lld",&op,&l,&r);
		if(l>r)  swap(l,r);
		if(op==0)
			update(1,1,n,l,r);
		if(op==1)
			printf("%lld\n",query(1,1,n,l,r));
	}
	return 0;
}
posted @ 2022-08-13 10:35  respect_lowsmile  阅读(42)  评论(0编辑  收藏  举报