线段树维护方差和区间开平方
线段树应用
线段树维护方差(线段树维护区间平方和)
例题: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;
}