线段树
前情提要,因为大家写树剖卡在线段树了,故写一篇博客来探讨线段树 帮助 抬走大家
单点修改的线段树
先贴代码
#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);
分别对应以上三行代码,因为我们是将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;
}
简单来讲就是,够的到左右边界就递归求和,反正线段树最终会被分成单个区间的叶子,不用担心精度问题
方法三(一的简化):
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);
}
}
}
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;//处理完清零
}
(画的不是太好,但基本是这么个意思)
有一点需要注意,当单点和区间修改混合时,单点也要更新,因为可能这时还有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.权值线段树
此时线段树的叶子代表每个值的出现次数,而他们的根则表示一段范围内的值的出现次数,常常搭配动态线段树来实现