线段树

前情提要,因为大家写树剖卡在线段树了,故写一篇博客来探讨线段树 帮助 抬走大家

单点修改的线段树

先贴代码
#include<bits/stdc++.h>
#define lson id<<1
#define rson id<<1|1
using namespace std;
const int N=1e6+12000;
struct node{
	int l,r,num;
	int ma,mi;
}tr[N<<2];
int a[N];
int n,m;
string str;
int ans=0;
int from,to;
void build(int id,int l,int r){
	tr[id].l=l;
	tr[id].r=r;
	if(l==r){
		tr[id].num=a[l];
		tr[id].ma=a[l];
		tr[id].mi=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(lson,l,mid);
	build(rson,mid+1,r);
	tr[id].num=tr[lson].num+tr[rson].num;
	tr[id].ma=max(tr[lson].ma,tr[rson].ma);
	tr[id].mi=min(tr[lson].mi,tr[rson].mi);
} 
//void update(int id,int x,int ad){
//	if(x>tr[id].r||x<tr[id].l) return; 
//	if(tr[id].l==tr[id].r){
//		tr[id].num+=ad;
//		tr[id].ma+=ad;
//		tr[id].mi+=ad;
//		return;
//	}
//	int mid=(tr[id].l+tr[id].r)/2;
//	update(lson,x,ad);
//	update(rson,x,ad);
//	tr[id].num=tr[lson].num+tr[rson].num; 
//	tr[id].ma=max(tr[lson].ma,tr[rson].ma);
//	tr[id].mi=min(tr[lson].mi,tr[rson].mi);
//}
//这样也可以
void update(int id,int x,int ad){
	if(tr[id].l==tr[id].r){
		tr[id].num+=ad;
		tr[id].ma+=ad;
		tr[id].mi+=ad;
		return;
	}
	int mid=(tr[id].l+tr[id].r)/2;
	if(x<=mid) update(lson,x,ad);
	else update(rson,x,ad);
	tr[id].num=tr[lson].num+tr[rson].num; 
	tr[id].ma=max(tr[lson].ma,tr[rson].ma);
	tr[id].mi=min(tr[lson].mi,tr[rson].mi);
}
//int getsum(int id,int l,int r){
//	if(r>=tr[id].r&&l<=tr[id].l){
//		return tr[id].num;
//	}
//	int mid=(tr[id].l+tr[id].r)/2;
//	int ans=0; 
//	if(l<=mid) ans+= getsum(lson,l,r);
//	if(r>mid) ans+= getsum(rson,l,r);
//  return ans;
//}
//这样也可以
int getsum(int id,int l,int r){
	if(r>=tr[id].r&&l<=tr[id].l){
		return tr[id].num;
	}
	int mid=(tr[id].l+tr[id].r)/2;
	if(mid<l) return getsum(rson,l,r);
	else if(mid>=r) return getsum(lson,l,r);
	else return getsum(lson,l,mid)+getsum(rson,mid+1,r);
}
int getmax(int id,int l,int r){
	if(r>=tr[id].r&&l<=tr[id].l){
		return tr[id].ma;
	}
	int maxn=0;
	int mid=(tr[id].r+tr[id].l)/2;
	if(mid<l) maxn=max(maxn,getmax(rson,l,r));
	else if(mid>=r) maxn=max(maxn,getmax(lson,l,r));
	else maxn=max({maxn,getmax(lson,l,mid),getmax(rson,mid+1,r)});
	return maxn;
}
int getmin(int id,int l,int r){
	if(r>=tr[id].r&&l<=tr[id].l){
		return tr[id].ma;
	}
	int minn=0x7fffffff;
	int mid=(tr[id].l+tr[id].r)/2;
	if(mid<l) minn=min(minn,getmin(rson,l,r));
	else if(mid>=r) minn=min(minn,getmin(lson,l,r));
	else minn=min({minn,getmin(lson,l,mid),getmin(rson,mid+1,r)});
	return minn;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	cin>>m;
	if(n) build(1,1,n);
	for(int i=1;i<=m;i++){
		cin>>str>>from>>to;
		if(str=="ADD"){
			update(1,from,to);
		}
		else{
			cout<<getsum(1,from,to)<<endl;
		}
	}
}

几个注意点
1.更新和建树跑到叶子时要return,这个千万不能忘

if(tr[id].l==tr[id].r){
		tr[id].num+=ad;
		tr[id].ma+=ad;
		tr[id].mi+=ad;
		return;
	}
if(l==r){
		tr[id].num=a[l];
		tr[id].ma=a[l];
		tr[id].mi=a[l];
		return;
	}

2.关于递归求值

方法一:

if(mid<l) return getsum(rs,l,r);
	else if(mid>=r) return getsum(ls,l,r);
	else return getsum(ls,l,mid)+getsum(rs,mid+1,r);

image
分别对应以上三行代码,因为我们是将l到mid定为lson,mid+1到r定为rson,所以查询的左边界要大于mid才能完全在右区间,而右边界仅需小于等于

方法二:

int getsum(int id,int l,int r){
	if(r>=tr[id].r&&l<=tr[id].l){
		return tr[id].num;
	}
	int mid=(tr[id].l+tr[id].r)/2;
	int ans=0; 
	if(l<=mid) ans+= getsum(lson,l,r);
	if(r>mid) ans+= getsum(rson,l,r);
  return ans;
}

image
简单来讲就是,够的到左右边界就递归求和,反正线段树最终会被分成单个区间的叶子,不用担心精度问题

方法三(一的简化):

int getsum(int id,int l,int r){
	if(l>tr[id].r||r<tr[id].l){
		return 0;
	}
	if(tr[id].l>=l&&tr[id].r<=r){
		return tr[id].sum;
	}
	return getsum(lson,l,r)+getsum(rson,l,r);
}

仅需要判断是否与当前线段树有交集就行

区间处理的线段树(lazy标记)

先贴代码
#include<bits/stdc++.h>
#define lson id<<1
#define rson id<<1|1
using namespace std;
const int N=1e6+1200;
int a[N];
int n,m;
struct node{
	int l,r,lazy,num;
}tr[N<<2];
void build(int id,int l,int r){
	tr[id].l=l;
	tr[id].r=r;
	if(l==r){
		tr[id].num=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(lson,l,mid);
	build(rson,mid+1,r);
	tr[id].num=tr[lson].num+tr[rson].num;
}
void pushup(int id){
	tr[lson].lazy+=tr[id].lazy;
	tr[rson].lazy+=tr[id].lazy;
	tr[lson].num+=(tr[lson].r-tr[lson].l+1)*tr[id].lazy;
	tr[rson].num+=(tr[rson].r-tr[rson].l+1)*tr[id].lazy;
	tr[id].lazy=0;
}
void update(int id,int l,int r,int ad){
	if(l>tr[id].r||r<tr[id].l){
		return;
	}
	if(tr[id].r<=r&&tr[id].l>=l){
		tr[id].num+=ad*(tr[id].r-tr[id].l+1);
		tr[id].lazy+=ad;
		return;
	}
	int mid=(tr[id].l+tr[id].r)/2;
	pushup(id);
	update(lson,l,r,ad);
	update(rson,l,r,ad);
	tr[id].num=tr[lson].num+tr[rson].num;
}
int getsum(int id,int l,int r){
	if(l>tr[id].r||r<tr[id].l){
		return 0;
	}
	if(tr[id].r<=r&&tr[id].l>=l){
		return tr[id].num;
	}
	int mid=(tr[id].l+tr[id].r)/2;
	pushup(id);
	return getsum(lson,l,mid)+getsum(rson,mid+1,r);
}
int main(){
	string str;
	int from,to,w;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	build(1,1,n);
	cin>>m;
	for(int i=1;i<=m;i++){
		cin>>str;
		if(str=="SUM"){
			cin>>from>>to;
			cout<<getsum(1,from,to)<<endl;
		}
		else {
			cin>>from>>to>>w;
			update(1,from,to,w);
		}
	}
}

我们的目的是,将区间内所有的线段树都加上对应的值,但问题是,他太多了,所以我们要将添加的要求暂时放一下,所以就有了lazy标记
void pushup(int id){
	tr[lson].lazy+=tr[id].lazy;
	tr[rson].lazy+=tr[id].lazy;//处理id以及它的子树对应的总和,将子树以及它的子子树的处理先放置一边
	tr[lson].num+=(tr[lson].r-tr[lson].l+1)*tr[id].lazy;
	tr[rson].num+=(tr[rson].r-tr[rson].l+1)*tr[id].lazy;
	tr[id].lazy=0;//处理完清零
}

image
(画的不是太好,但基本是这么个意思)
有一点需要注意,当单点和区间修改混合时,单点也要更新,因为可能这时还有lazy标记,导致传数的错误,例如[HAOI2015] 树上操作

以下内容在这里有详解,不多赘述

3.动态线段树

动态树就是动态开点,此时它的左右儿子将不再是id<<1和id<<1|1,而是根据需要才有,这时树的l,r不再表示边界,而是左右儿子,这就需要我们在更新查询时引用左边界和右边界
以数列操作为例

点击查看代码
#include<bits/stdc++.h>
#define lson tr[id].l
#define rson tr[id].r
using namespace std;
const int N=1e6+20;
int a[N];
struct node{
	int l,r,sum;
}tr[N<<2];
int n,m;
int num;
int rt;
void update(int &id,int l,int r,int x,int ad){
	if(id==0) id=++num;
	if(l==r){
		tr[id].sum+=ad;return;
	}
	int mid=(l+r)/2;
	if(x<=mid) update(lson,l,mid,x,ad);
	else update(rson,mid+1,r,x,ad);
	tr[id].sum=tr[lson].sum+tr[rson].sum;
}
int query(int id,int l,int r,int L,int R){
	if(l>R||r<L)return 0;
	if(id==0) return 0;
	if(l>=L&&r<=R) return tr[id].sum;
	int mid=(l+r)/2;
	 return query(lson,l,mid,L,R)+query(rson,mid+1,r,L,R);
}
int main(){
	int from,to;
	string str;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		update(rt,1,n,i,a[i]);
		rt=1;
	}
	cin>>m;
	for(int i=1;i<=m;i++){
		cin>>str>>from>>to;
		if(str=="ADD"){
			update(rt,1,n,from,to);
			rt=1;
		}
		else{
			cout<<query(rt,1,n,from,to)<<endl;
			rt=1;
		}
	}
}

4.权值线段树

此时线段树的叶子代表每个值的出现次数,而他们的根则表示一段范围内的值的出现次数,常常搭配动态线段树来实现

posted @ 2024-05-08 12:10  shaoyufei  阅读(11)  评论(0编辑  收藏  举报