2020.01.24日常总结兼线段树、树状数组实践题略讲

前 言 \color{green}{前言}

  • 作为提高必备的两大数据结构,线段树和树状数组非常重要。本日记将举一些线段树和树状数组的实例,带大家了解线段树和树状数组的作用。
  • 本日记的题目难度大概在 提 高 + / 省 选 \color{red}{提高+/省选} +/左右。不会讲线段树和树状数组的原理。

实 例 \color{green}{实例}

洛 谷 P 1471    方 差 \color{blue}{洛谷P1471\ \ 方差} P1471  

【 题 意 】 : \color{orange}{【题意】:} 蒟蒻HansBug在一本数学书里面发现了一个神奇的数列,包含 N N N个实数。他想算算这个数列的平均数和方差。数据带修改。

【注】:方差:方差是计算一个数列数据波动情况的重要量,记为 s 2 s^2 s2。记数据为 A A A P P P为平均数,则方差计算公式为:
s 2 = ∑ i = 1 n ( A i − P ) 2 n s^2=\frac{\sum\limits_{i=1}^{n} (A_i-P)^2}{n} s2=ni=1n(AiP)2

【 思 路 】 : \color{orange}{【思路】:} 我们把方差的公式稍微修改一下,得:

2020.01.24日常总结兼线段树、树状数组实践题略讲_第1张图片
(以上图片选自一篇洛谷题解)

所以,现在我们只需维护区间的平方和与区间和即可。

而对于区间的平方和,如果我们令区间同时加上一个数 x x x,我们有:
2020.01.24日常总结兼线段树、树状数组实践题略讲_第2张图片
(以上图片依旧选自一篇洛谷题解)

然后,我们就可以直接用线段树维护求解。

【 代 码 】 : \color{orange}{【代码】:}

#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
	char c=0;int x=0;bool f=0;
	while (!g(c)) f=c=='-',c=gc;
	while (g(c)) x=x*10+c-48,c=gc;
	return f?-x:x;
}
const int N=1e5+100;
double sum[N<<2],add[N<<2];
double seg[N<<2],a[N];
inline void pushup(int o){
	sum[o]=sum[o<<1]+sum[o<<1|1];
	seg[o]=seg[o<<1]+seg[o<<1|1];
}
inline void pushdown(int o,int l,int r){
	double tag=add[o];add[o]=0;
	add[o<<1|1]+=tag;add[o<<1]+=tag;
	register int mid=(l+r)>>1;//注意顺序,一定要先修改seg,再修改sum
	seg[o<<1]+=2*tag*sum[o<<1]+(mid-l+1)*tag*tag;
	seg[o<<1|1]+=2*tag*sum[o<<1|1]+(r-mid)*tag*tag;
	sum[o<<1]+=tag*(mid-l+1);sum[o<<1|1]+=tag*(r-mid);
}
void build(int o,int l,int r){
	add[o]=0.0;
	if (l==r){
		sum[o]=a[l];
		seg[o]=a[r]*a[r];
		return;
	}
	register int mid=(l+r)>>1;
	build(o<<1,l,mid);
	build(o<<1|1,mid+1,r);
	pushup(o);return;
}
void updata(int o,int l,int r,int p,int q,double v){
	if (l>q||r<p) return;
	if (p<=l&&r<=q){
		seg[o]+=2*v*sum[o]+(r-l+1)*v*v;
		sum[o]+=v*(r-l+1);add[o]+=v;return;
	}
	if (add[o]) pushdown(o,l,r);
	register int mid=(l+r)>>1;
	updata(o<<1,l,mid,p,q,v);
	updata(o<<1|1,mid+1,r,p,q,v);
	pushup(o);return;
}
double query_sum(int o,int l,int r,int p,int q){
	if (l>q||r<p) return 0.0;
	if (p<=l&&r<=q) return sum[o];
	if (add[o]) pushdown(o,l,r);
	register int mid=(l+r)>>1;
	register double answer=0.0;
	answer+=query_sum(o<<1,l,mid,p,q);
	answer+=query_sum(o<<1|1,mid+1,r,p,q);
	return answer;
}
double query_squ(int o,int l,int r,int p,int q){
	if (l>q||r<p) return 0.0;
	if (p<=l&&r<=q) return seg[o];
	if (add[o]) pushdown(o,l,r);
	register int mid=(l+r)>>1;
	register double answer=0.0;
	answer+=query_squ(o<<1,l,mid,p,q);
	answer+=query_squ(o<<1|1,mid+1,r,p,q);
	return answer;
}
int n,m,opt,l,r;
int main(){
	freopen("t1.in","r",stdin);
	n=read();m=read();
	for(int i=1;i<=n;i++)
		scanf("%lf",&a[i]);
	build(1,1,n);
	for(int i=1;i<=m;i++){
		opt=read();l=read();r=read();
		if (opt==1){
			register double v;
			scanf("%lf",&v);
			updata(1,1,n,l,r,v);
		}
		else{
			double ave=query_sum(1,1,n,l,r)/(r-l+1.0);
			if (opt==2) printf("%.4lf\n",ave);
			else printf("%.4lf\n",-ave*ave+query_squ(1,1,n,l,r)/(r-l+1));
		}
	}
	return 0;
} 

洛 谷 P 1558    色 板 游 戏 \color{blue}{洛谷P1558\ \ 色板游戏} P1558  

【 题 意 】 : \color{orange}{【题意】:} 阿宝上学了,今天老师拿来了一块很长的涂色板。

色板长度为 L L L L L L是一个正整数,所以我们可以均匀地将它划分成 L L L 1 1 1厘米长的小方格。并从左到右标记为 1 , 2 , . . . L 1, 2, ... L 1,2,...L

现在色板上只有一个颜色,老师告诉阿宝在色板上只能做两件事:

  • C A B C 指在 A A A B B B 号方格中涂上颜色 C C C
  • P A B 指老师的提问: A A A B B B号方格中有几种颜色。

学校的颜料盒中一共有 T T T 种颜料。为简便起见,我们把他们标记为 1 , 2 , . . . T 1, 2, ... T 1,2,...T。 开始时色板上原有的颜色就为 1 1 1号色。 面对如此复杂的问题,阿宝向你求助,你能帮助他吗?

【 思 路 】 : \color{orange}{【思路】:} 乍一看这题很难,但是 1 ≤ T ≤ 30 1 \leq T \leq 30 1T30,所以我们可以把区间每个位置的颜色用二进制保存,然后就可以用线段树维护了。

【 代 码 】 : \color{orange}{【代码】:}

const int N=1e5+100;
#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
	char c=0;int x=0;bool f=0;
	while (!g(c)) f=c=='-',c=gc;
	while (g(c)) x=x*10+c-48,c=gc;
	return f?-x:x;
}
int sum[N<<2],add[N<<2];
inline void pushup(int o){
	sum[o]=sum[o<<1]|sum[o<<1|1];
}
inline void pushdown(int o){
	int tag=add[o];add[o]=0;
	add[o<<1]=tag;add[o<<1|1]=tag;
	sum[o<<1]=tag;sum[o<<1|1]=tag;
}
void build(int o,int l,int r){
	if (l==r){
		sum[o]=1;return;
	}
	register int mid=(l+r)>>1;
	build(o<<1,l,mid);
	build(o<<1|1,mid+1,r);
	pushup(o);return;
}
void updata(int o,int l,int r,int p,int q,int v){
	if (l>q||r<p) return;
	if (p<=l&&r<=q){
		add[o]=1<<(v-1);
		sum[o]=1<<(v-1);
		return;
	}
	if (add[o]) pushdown(o);
	register int mid=(l+r)>>1;
	updata(o<<1,l,mid,p,q,v);
	updata(o<<1|1,mid+1,r,p,q,v);
	pushup(o);return;
}
int query(int o,int l,int r,int p,int q){
	if (l>q||r<p) return 0;
	if (p<=l&&r<=q) return sum[o];
	if (add[o]) pushdown(o);
	register int mid=(l+r)>>1;
	register int answer=0;
	answer|=query(o<<1,l,mid,p,q);
	answer|=query(o<<1|1,mid+1,r,p,q);
	return answer;
}
int n,m,color;//n:L;m:O;color:T
int calc(int x){//计算x的二进制中有多少个1 
	register int cnt=0;
	for(int i=0;i<color;i++)
		if (x&(1<<i))
			cnt++;
	return cnt;
}
int main(){
	n=read();color=read();
	m=read();build(1,1,n);
	for(int i=1;i<=m;i++){
		register char opt;cin>>opt;
		register int l=read(),r=read();
		if (l>r) swap(l,r);//Don't forget it!!!
		if (opt=='C'){
			register int v=read();
			updata(1,1,n,l,r,v);
		}
		else printf("%d\n",calc(query(1,1,n,l,r)));
	}
	return 0;
}

洛 谷 P 2184    贪 婪 大 陆 \color{blue}{洛谷P2184\ \ 贪婪大陆} P2184  

【 题 意 】 : \color{orange}{【题意】:} 面对蚂蚁们的疯狂进攻,小FFTower defence宣告失败……人类被蚂蚁们逼到了Greed Island上的一个海湾。现在,小FF的后方是一望无际的大海, 前方是变异了的超级蚂蚁。小FF还有大好前程,他可不想命丧于此, 于是他派遣手下最后一批改造SCV布置地雷以阻挡蚂蚁们的进攻。

小FF最后一道防线是一条长度为 N N N的战壕,小FF拥有无数多种地雷,而SCV每次可以在 [ L , R ] [ L , R ] [L,R]区间埋放同一种不同于之前已经埋放的地雷。 由于情况已经十万火急,小FF在某些时候可能会询问你在 [ L ′ , R ′ ] [ L' , R'] [L,R] 区间内有多少种不同的地雷, 他希望你能尽快的给予答复。

【 思 路 】 : \color{orange}{【思路】:} 每次埋地雷,我们可以看作以下两个事件:

  • L L L放入一个(,表示一种新地雷的开始
  • R R R放入一个),表示一种地雷的结束位置

对于查询操作,我们先查询 R R R以前有多少中地雷,即查询 R R R以前有多少个(,当然有些地雷可能不在 [ L , R ] [L,R] [L,R]内,它们有一个共性:即结束位置 < L <L,所以我们用 R R R以前的(的数量减去 L L L以前的)的数量。

于是,我们分别开两个树状数组维护()即可。总的时间复杂度: O ( M × l o g N ) O(M \times log N) O(M×logN)

【 代 码 】 : \color{orange}{【代码】:}

#define gc getchar()
#define g(c) isdigit(c)
inline int read(){
	char c=0;int x=0;bool f=0;
	while (!g(c)) f=c=='-',c=gc;
	while (g(c)) x=x*10+c-48,c=gc;
	return f?-x:x;
}
const int N=1e5+1e3;
typedef long long ll;
ll c[N][3];int n,m,opt,l,r;
inline int F(int x){
	return x&(-x);
}
inline void updata(int x,int p){
	for(;x<=n;x+=F(x)) c[x][p]++;
}
inline ll query(int x,int p){
	register ll ans=0ll;
	for(;x;x-=F(x))
		ans+=c[x][p];
	return ans;
}
int main(){
	n=read();m=read();
	for(int i=1;i<=m;i++){
		opt=read();l=read();r=read();
		if (opt==1){
			updata(l,1);
			updata(r,2);
		}
		else printf("%lld\n",query(r,1)-query(l-1,2));
	}
	return 0;
}

你可能感兴趣的:(线段树,树状数组,日记)