线段树详解
线段树,顾名思义,由线段构成的树;非常实用的数据结构;
线段树由浅入深分为三各层次
1.单点修改+区间查询
2.区间修改+单点查询
3.区间修改+区间查询
1单点修改+区间查询
先上问题:
codvs1080
读完题可能大家有很多方法,但面对庞大的数据,会发现都略有欠缺,所以就轮到线段树上场了;
假设数列为1 2 3 4 5 6 7 8
那么线段树就是这样的
每个节点维护某个区间的左端点l和右端点r,因为题目要求维护区间和,那么节点信息再加一条该区间的和s;
那么一刻整整齐齐的线段树就完成了;
对于题目,对该线段树有两个操作
1.某个数增加后一些包含此数的区间和也要改变(即节点的s)
2.查询某段区间的和;
逐个击破
1.假设第x个数增加k,那么从根节点开始,判断该节点维护的区间包不包含第x个数,若包含,s+=k,再依次遍历左右儿子;若某个区间不包含x,那么不必继续,继续往下更不可能包括;
2.查询分三种情况:
a.该节点区间与查询区间完全分离,return 0;
b.节点区间是查询区间的子集,return s;
c.节点区间与查询区间部分相交,return lson(左儿子)的返回值+rson的返回值;
说了那么多直接上代码吧
```#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 100010
using namespace std;
struct Node{int l,r,ls,rs,s;};
vector<Node> N;
int n,m,a[MAXN];
void build_tree(int v){//建树
if(N[v].l==N[v].r){N[v].s=a[N[v].l];return;}
int mid=(N[v].l+N[v].r)>>1;//二分区间
N.push_back((Node){N[v].l,mid,0,0,0}); N[v].ls=N.size()-1;//生成左儿子
N.push_back((Node){mid+1,N[v].r,0,0,0}); N[v].rs=N.size()-1;//生成右儿子
build_tree(N[v].ls); build_tree(N[v].rs);
N[v].s=N[N[v].ls].s+N[N[v].rs].s;//和为左儿子+右儿子区间和
}
void Change(int v,int addnode,int val){
if(N[v].l<=addnode&&N[v].r>=addnode){//包含在区间中
N[v].s+=val;
if(N[v].l==N[v].r) return;
Change(N[v].ls,addnode,val);
Change(N[v].rs,addnode,val);
}
}
int Query(int v,int l,int r){
if(N[v].l>=l&&N[v].r<=r) return N[v].s;//包含
else if(N[v].l>r||N[v].r<l) return 0;//分离
else return Query(N[v].ls,l,r)+Query(N[v].rs,l,r);//相交
}
int main(){
scanf("%d",&n);
N.push_back((Node){1,n,0,0,0});
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build_tree(0);
// for(int i=1;i<=n;i++) cout<<a[i]<<endl;
scanf("%d",&m);
while(m--){
int type,a,b; scanf("%d%d%d",&type,&a,&b);
if(type==1) Change(0,a,b);
if(type==2) printf("%d\n",Query(0,a,b));
}
return 0;
}
区间修改单点查询
区间修改就不那么好办了,把区间每个数循环加上k吗?那复杂度又炸了;下面上王牌—lazy tag----懒惰标记,也是顾名思义,我才懒得加数呢
,我记着那些区间要加k,哪些不加,等你问的时候我再算,方便了许多,也提高的很多效率。
所以每个节点需要维护新的信息,N[v].d,表示该节点维护的区间每个数需加上d.
那么进行操作2时,若N[v].l>=l&&N[v].r<=r即被包含那么N[v].d+=k
那么有一个问题,假如某操作将1–8加上k,我们就将节点1打上懒惰标记,可是1的子节点区间为1–4的和5—8也应加上怎么办呢; so easy,询问的时候讲祖先的懒惰标记一层层累加下来就行了;
代码与之前差不多
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iterator>
#define MAXN 100010
using namespace std;
struct Node{int l,r,ls,rs,s;};
vector<Node> N;
int n,m,a[MAXN];
void build_tree(int v){
if(N[v].l==N[v].r) return;
int mid=(N[v].l+N[v].r)>>1;
N.push_back((Node){N[v].l,mid,0,0,0}); N[v].ls=N.size()-1;
N.push_back((Node){mid+1,N[v].r,0,0,0}); N[v].rs=N.size()-1;
build_tree(N[v].ls); build_tree(N[v].rs);
//因为是单点查询就没必要把区间和求出来了
}
void Change(int v,int l,int r,int val){
if(N[v].l>r||N[v].r<l) return;//分离
else if(N[v].l>=l&&N[v].r<=r) N[v].s+=val;//包含
else{//相交
Change(N[v].ls,l,r,val);
Change(N[v].rs,l,r,val);
}
}
void Query(int v,int pos,int sum){//sum记录累加下来的懒惰标记
if(N[v].l>pos||N[v].r<pos) return;//分离
if(N[v].l==N[v].r){//找到符合要求的叶子节点
printf("%d\n",sum+a[N[v].l]+N[v].s);
return;
}
Query(N[v].ls,pos,sum+N[v].s);//寻找叶子节点,因为是单点查询
Query(N[v].rs,pos,sum+N[v].s);
}
int main(){
scanf("%d",&n);
N.push_back((Node){1,n,0,0,0});
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build_tree(0);
scanf("%d",&m);
while(m--){
int type; scanf("%d",&type);
if(type==1){
int a,b,c; scanf("%d%d%d",&a,&b,&c);
Change(0,a,b,c);
// for(int i=0;i<N.size();i++) cout<<i<<' '<<N[i].l<<' '<<N[i].r<<' '<<N[i].ls<<' '<<N[i].rs<<' '<<N[i].s<<endl;
}
if(type==2){
int x; scanf("%d",&x);
Query(0,x,0);
}
}
return 0;
}
3.区间修改+区间查询
codvs1082
区间查询就无法把祖先的懒惰标记累加下来了,因为给的查询区间有可能在线段树上分为许多块,这是很难处理的。
那么就在原基础上加入push_down操作,将当前节点的懒惰标记推给自己的儿子,并把自己的s更新
如下
void push_down(int v){
N[v].s=N[v].s+N[v].d*(LL)(N[v].r-N[v].l+1);
if(N[v].l<N[v].r) N[N[v].ls].d+=N[v].d , N[N[v].rs].d+=N[v].d;//若有儿子
N[v].d=0;//该节点懒惰标记清零
}
这样维护了每个区间正确的s和d,剩下的操作就与上面类似了
直接上代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iterator>
#define LL long long
#define MAXN 200010
using namespace std;
struct Node{int l,r,ls,rs;LL s,d;};
vector<Node> N;
int n,m,a[MAXN];
void push_down(int v){
N[v].s=N[v].s+N[v].d*(LL)(N[v].r-N[v].l+1);
if(N[v].l<N[v].r) N[N[v].ls].d+=N[v].d , N[N[v].rs].d+=N[v].d;
N[v].d=0;
}
void build_tree(int v){
if(N[v].l==N[v].r){N[v].s=a[N[v].l];return;}
int mid=(N[v].l+N[v].r)>>1;
N.push_back((Node){N[v].l,mid,0,0,0}); N[v].ls=N.size()-1;
N.push_back((Node){mid+1,N[v].r,0,0,0}); N[v].rs=N.size()-1;
build_tree(N[v].ls); build_tree(N[v].rs);
N[v].s=N[N[v].ls].s+N[N[v].rs].s;
}
void Change(int v,int l,int r,int val){
push_down(v);//由于在上一层需要用到正确的v节点的信息,所以在避免下行的返回导致信息丢失先更新信息
if(N[v].l>r||N[v].r<l) return;
if(N[v].l>=l&&N[v].r<=r){
N[v].d+=val;
push_down(v);
}
else{
Change(N[v].ls,l,r,val);
Change(N[v].rs,l,r,val);
N[v].s=N[N[v].ls].s+N[N[v].rs].s;//
}
}
LL Query(int v,int l,int r){
if(N[v].l>r||N[v].r<l) return 0;
push_down(v);
if(N[v].l>=l&&N[v].r<=r) return N[v].s;
return Query(N[v].ls,l,r)+Query(N[v].rs,l,r);
}
int main(){
scanf("%d",&n);
N.push_back((Node){1,n,0,0,0});
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build_tree(0);
scanf("%d",&m);
while(m--){
int type; scanf("%d",&type);
if(type==1){
int a,b,c; scanf("%d%d%d",&a,&b,&c);
Change(0,a,b,c);
}
if(type==2){
int x,y; scanf("%d%d",&x,&y);
printf("%lld\n",Query(0,x,y));
}
}
return 0;
}
就这么多啦!!谢谢!!