线段树
相比于树状数组只能解决计算前缀和的问题,线段树则更为通用,我们可以用线段树实现树状数组所有的功能,但是线段树实现较为复杂,通常可以直接套用模板,再针对具体问题做一些变化,这里不再赘述;
线段树利用冗余存储的方式来加快搜索速度,这是时间与空间折中的经典思想;线段树的形状是一颗完全二叉树,其深度为log2(n)取上整,算下来所有线段树中的节点数不会超会数据个数的4倍,这样我们就可以在初始化时直接设置为原始数据数目的4倍即可;
下面给出一个模板以及例题:
线段树模板
相对而言递归实现更容易理解,这里也只给出递归实现;
int n;
long long arr[400001];
long long lazy[400001];
long long num[100001];
void up(int rt){//更新
arr[rt]=arr[rt<<1]+arr[rt<<1|1];
return;
}
//初始化建立线段树
void build(int l,int r,int rt){//原始数据区间左端,区间右端,线段树中保存原始数据中这一段区间的节点编号
if(l==r){
arr[rt]=num[l];
return;
}
int m=(l+r)/2;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
up(rt);
return;
}
void down(int ln,int rn,int rt){
//延迟更新
//为了避免区间更新时间复杂度退化为O(n),这里使用惰性更新的原则
//也即用到时才会更新,不用时就不更新,值更新到使用点为止
//更新线段树中左右节点的值
arr[rt<<1]+=ln*lazy[rt];
arr[rt<<1|1]+=rn*lazy[rt];
//更新左右节点中惰性标记的值
lazy[rt<<1]+=lazy[rt];
lazy[rt<<1|1]+=lazy[rt];
lazy[rt]=0;
}
//点更新
void add(int x,long long v,int l,int r,int rt){
if(l==r){
arr[rt]+=v;
return;
}
int m=(l+r)/2;
down(m-l+1,r-m,rt);//同时有点更新与区间更新时,这里要加延迟更新的操作
if(x<=m)add(x,v,l,m,rt<<1);
else add(x,v,m+1,r,rt<<1|1);
up(rt);
return;
}
//区间更新
void update(int x,int y,long long v,int l,int r,int rt){
if(x<=l&&y>=r){
arr[rt]+=(r-l+1)*v;
lazy[rt]+=v;
return;
}
int m=(l+r)/2;
down(m-l+1,r-m,rt);
if(x<=m)update(x,y,v,l,m,rt<<1);
if(y>m)update(x,y,v,m+1,r,rt<<1|1);
up(rt);
return;
}
//区间查询
long long query(int x,int y,int l,int r,int rt){
if(x<=l&&y>=r){
return arr[rt];
}
int m=(l+r)/2;
down(m-l+1,r-m,rt);
long long ans=0;
if(x<=m)ans+=query(x,y,l,m,rt<<1);
if(y>m)ans+=query(x,y,m+1,r,rt<<1|1);
return ans;
}
例题
#include<bits/stdc++.h>
using namespace std;
int n;
long long arr[400001];
long long lazy[400001];
long long num[100001];
void up(int rt){//更新
arr[rt]=arr[rt<<1]+arr[rt<<1|1];
return;
}
//初始化建立线段树
void build(int l,int r,int rt){//原始数据区间左端,区间右端,线段树中保存原始数据中这一段区间的节点编号
if(l==r){
arr[rt]=num[l];
return;
}
int m=(l+r)/2;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
up(rt);
return;
}
void down(int ln,int rn,int rt){
//延迟更新
//为了避免区间更新时间复杂度退化为O(n),这里使用惰性更新的原则
//也即用到时才会更新,不用时就不更新,值更新到使用点为止
//更新线段树中左右节点的值
arr[rt<<1]+=ln*lazy[rt];
arr[rt<<1|1]+=rn*lazy[rt];
//更新左右节点中惰性标记的值
lazy[rt<<1]+=lazy[rt];
lazy[rt<<1|1]+=lazy[rt];
lazy[rt]=0;
}
//点更新
void add(int x,long long v,int l,int r,int rt){
if(l==r){
arr[rt]+=v;
return;
}
int m=(l+r)/2;
down(m-l+1,r-m,rt);//同时有点更新与区间更新时,这里要加延迟更新的操作
if(x<=m)add(x,v,l,m,rt<<1);
else add(x,v,m+1,r,rt<<1|1);
up(rt);
return;
}
//区间更新
void update(int x,int y,long long v,int l,int r,int rt){
if(x<=l&&y>=r){
arr[rt]+=(r-l+1)*v;
lazy[rt]+=v;
return;
}
int m=(l+r)/2;
down(m-l+1,r-m,rt);
if(x<=m)update(x,y,v,l,m,rt<<1);
if(y>m)update(x,y,v,m+1,r,rt<<1|1);
up(rt);
return;
}
//区间查询
long long query(int x,int y,int l,int r,int rt){
if(x<=l&&y>=r){
return arr[rt];
}
int m=(l+r)/2;
down(m-l+1,r-m,rt);
long long ans=0;
if(x<=m)ans+=query(x,y,l,m,rt<<1);
if(y>m)ans+=query(x,y,m+1,r,rt<<1|1);
return ans;
}
int main(){
//初始化lazy数组
memset(lazy,0,sizeof(lazy));
int m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&num[i]);
}
build(1,n,1);
for(int i=0;i<m;i++){
char str[2];
scanf("%s",&str);
if(str[0]=='Q'){
int a;
scanf("%d",&a);
printf("%lld \n",query(a,a,1,n,1));
}else{
int a,b;
long long v;
scanf("%d %d %lld",&a,&b,&v);
update(a,b,v,1,n,1);
}
}
return 0;
}