线段树(Segment tree)是一种二叉树形数据结构,1977年由Jon Louis Bentley发明,用以储存区间或线段,并且允许快速查询结构内包含某一点的所有区间。
一个包含 n个区间的线段树,空间复杂度为 O(nlog n),查询的时间复杂度则为 O(log n+k),其中 k是符合条件的区间数量。(引自维基百科)
注意
1.线段树是一棵满二叉树;
第i层有2^(i-1)个节点;
前i层 共有2^i -1个节点;
一般要开四倍空间(具体证明不太明白);
对于节点m,m<<1(m*2)是它的左儿子,m<<1|1(m*2+1)是它的右儿子
2.线段树的查询和修改都是O(log n)的;
3.使用线段树的操作,必须满足区间可加性
操作:
1。建树
结构体:维护l,r,题目要求维护的值(sum,max,min等)以sum为例
struct seg_tree{
int l,r;
long long sum;//要维护的值
int ax;//标记
}t[N<<2];
void updata(int m){//回溯数据更改
t[m].sum=t[m<<1].sum+t[m<<1|1].sum;
return ;
}
void build(int m,int ll,int rr){//建树
t[m].l=ll,t[m].r=rr;
if(ll==rr){
t[m].sum=num[ll];
return ;
}
int mid=ll+rr>>1;
build(m<<1,ll,mid);//递归处理左儿子
build(m<<1|1,mid+1,rr);//右儿子
updata(m);//回溯数据收集
return ;
}
2。修改
单点修改
void change(int m,int wz,int w){
if(t[m].l==t[m].r){//找到目标节点
t[m].sum+=w;//更改目标节点值
return ;
}
int mid=t[m].l+t[m].r>>1;
if(wz<=mid) change(m<<1,wz,w);//目标在左儿字部
else change(m<<1|1,wz,w);//目标在右儿子部
updata(m);//回溯数据
return ;
}
区间修改
暴力扫一遍超时———打标记!!
注:
标记:对节点本身已做过操作,对儿子未做过操作;
下放标记后,自身标记要清空,防止重复执行操作
修改后要注意在回溯时修改父亲节点的相应维护值,即updata
void add(int m,int v){//打标记
t[m].sum+=(t[m].r-t[m].l+1)*v;//对自己执行操作
t[m].ax+=v;
return ;
}
void spread(int m){//标记下放
if(t[m].ax){
add(m<<1,t[m].ax);
add(m<<1|1,t[m].ax);
t[m].ax=0;//清空自身标记
}
return ;
}
void change(int m,int ll,int rr,int v){
if(t[m].l>=ll&&t[m].r<=rr){
//当前区间被完全包含在要修改的区间内
add(m,v);
return ;
}
spread(m);
int mid=t[m].l+t[m].r>>1;
if(mid>=ll) change(m<<1,ll,rr,v);
if(mid<rr) change(m<<1|1,ll,rr,v);
updata(m);
return ;
}
3。查询
注:查询时要先下放区间修改的标记;
单点查询
long long ask(int m,int wz){
if(t[m].l==t[m].r) return v[wz]+t[m].ax;
spread(m);
int mid=t[m].l+t[m].r>>1;
if(wz<=mid) return ask(m<<1,wz);
else return ask(m<<1|1,wz);
}
区间查询
long long ask(int m,int ll,int rr){
if(t[m].l>=ll&&t[m].r<=rr) return t[m].sum;
spread(m);long long ans=0;
int mid=t[m].l+t[m].r>>1;
if(mid>=ll) ans+=ask(m<<1,ll,rr);
if(mid<rr) ans+=ask(m<<1|1,ll,rr);
return ans;
}
sys学长的笔记
例题
线段树练习1
单点修改,区间查询;
#include<iostream>
#include<cstdio>
using namespace std;
const int N=100000;
int n,q,x,y,z,v[N];
struct seg_tree{
int l,r;
long long sum;
}t[N<<2];
void updata(int m){
t[m].sum=t[m<<1].sum+t[m<<1|1].sum;
return ;
}
void build(int m,int ll,int rr){
t[m].l=ll;t[m].r=rr;
if(ll==rr){
t[m].sum=v[ll];
return ;
}
int mid=ll+rr>>1;
build(m<<1,ll,mid);
build(m<<1|1,mid+1,rr);
updata(m);
return ;
}
void change(int m,int wz,int w){
if(t[m].l==t[m].r){
t[m].sum+=w;
return ;
}
int mid=t[m].l+t[m].r>>1;
if(wz<=mid) change(m<<1,wz,w);
else change(m<<1|1,wz,w);
updata(m);
return ;
}
long long ask(int m,int ll,int rr){
if(t[m].l>=ll&&t[m].r<=rr) return t[m].sum;
int mid=t[m].l+t[m].r>>1;
long long ans=0;
if(mid>=ll) ans+=ask(m<<1,ll,rr);
if(mid<rr) ans+=ask(m<<1|1,ll,rr);
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&v[i]);
build(1,1,n);
// for(int i=1;i<=n*4;i++) printf("i=%d,l=%d,r=%d,sum=%d\n",i,t[i].l,t[i].r,t[i].sum);
scanf("%d",&q);
while(q--){
scanf("%d%d%d",&x,&y,&z);
if(x==1) change(1,y,z);
else if(x==2) printf("%lld\n",ask(1,y,z));
}
return 0;
}
线段树练习2
区间修改,单点查询;
#include<iostream>
#include<cstdio>
using namespace std;
const int N=100000+50;
int n,p,a,x,y,z,v[N];
struct seg_tree{
int l,r,ax;
}t[N<<2];
void build(int m,int ll,int rr){
t[m].l=ll,t[m].r=rr;
if(ll==rr) return ;
int mid=ll+rr>>1;
build(m<<1,ll,mid);
build(m<<1|1,mid+1,rr);
return ;
}
void add(int m,int vv){
t[m].ax+=vv;
}
void spread(int m){
if(t[m].ax) {
add(m<<1,t[m].ax);
add(m<<1|1,t[m].ax);
t[m].ax=0;
}
return ;
}
void change(int m,int ll,int rr,int vv){
if(t[m].l>=ll&&t[m].r<=rr) {
add(m,vv);return;
}
int mid=t[m].l+t[m].r>>1;
spread(m);
if(mid>=ll) change(m<<1,ll,rr,vv);
if(mid<rr) change(m<<1|1,ll,rr,vv);
return ;
}
int ask(int m,int wz){
if(t[m].l==t[m].r&&t[m].l==wz) return v[wz]+t[m].ax;
int mid=t[m].l+t[m].r>>1;
spread(m);
if(wz<=mid) return ask(m<<1,wz);
else return ask(m<<1|1,wz);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&v[i]);
build(1,1,n);
// for(int i=1;i<=n*4;i++) printf("i=%d,l=%d,r=%d\n",i,t[i].l,t[i].r);
scanf("%d",&p);
while(p--){
scanf("%d",&a);
if(a==1) scanf("%d%d%d",&x,&y,&z),change(1,x,y,z);
if(a==2) scanf("%d",&y),printf("%d\n",ask(1,y));
}
}
线段树练习3
区间修改,区间查询;