目录html
线段树是一种二叉搜索树,与区间树类似,它将一个区间划分红一些单元区间,每一个单元区间对应线段树中的一个叶结点。对于线段树中的每个非叶子节点$[a,b]$,它的左儿子表示的区间为$[a,(a+b)/2]$,右儿子表示的区间为$[(a+b)/2+1,b]$。所以线段树是平衡二叉树,最后的叶子节点数目为$N$,即整个线段区间的长度。使用线段树能够快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为$O(logN)$。而未优化的空间复杂度为$4N$node
大家可能会问什么是区间树,我也不知道。
$如上图就是一颗[1,8]的线段树。$ios
$每一个节点的左孩子区间范围为[l,mid],右孩子为[mid+1,r]。$
$对于节点k,左孩子为k * 2,右孩子为k * 2 + 1,和二叉树同样$数组
$线段树的基础操做主要有5个:建树、单点查询、单点修改、区间查询、区间修改。$数据结构
可使用结构体也可使用数组,看我的喜爱。优化
struct node{ int l,r,w;//l,r分别表示区间左右端点,w表示区间和 }tree[MAXN*4+1];//注意线段树要开四倍空间。
void build(int l,int r,int now){ tree[now].l=l,tree[now].r=r;//记下now这个节点所表示的区间。 if(l==r){//now节点为叶子结点。 scanf("%d",tree[now].w);//读入叶子结点的值。 return;//不用进行下面的了。 } int mid=(l+r)>>1;//>>1至关于/2 build(l,mid,now<<1);//<<1至关于*2 build(mid+1,r,now<<1|1);//<<1|1至关于*2+1 tree[now].w=tree[now<<1].w+tree[now<<1|1].w;//更新区间和 }
询问第$x$个点的值。ui
void ask_single(int x,int now){ if(tree[now].l==tree[now].r){//叶子结点,即最终答案。 ans=tree[now].w; return; } int mid=(tree[now].l+tree[now].r)>>1;//计算区间的中点。 if(x<=mid){ ask_single(x,now<<1);//查找该点的左孩子 }else{ ask_single(x,now<<1|1);//查找该点的有孩子 } }
给第$x$个点加上$y$。(和单点查询差很少qwq)spa
void updata_single(int x,int y,int now){ if(tree[now].l==tree[now].r){ tree[now].w+=y; return; } int mid=(tree[now].l+tree[now].r)>>1; if(x<=mid){ updata_single(x,y,now<<1); }else{ updata_single(x,y,now<<1|1); } tree[now].w=tree[now<<1].w+tree[now<<1|1].w;//单点修改后要更新区间和 }
修改的过程像上图同样递归修改,当修改完单点后再去更新上面的区间和。code
建议先看下面的区间查询。
将$[x,y]$区间每个数加上$x$。
区间里都是点,进行$y-x+1$次单点修改。(太慢了,应该不会有人用吧。)htm
void update_range(int x,int y,int c){ for(int i=x;i<=y;++i){ update_single(i,c,1); } }
上面这个作法数据不水的话就$TLE$了(在线段树1中只能拿$70$分)。
将某区间每个数加上$x$,这个是能够加优化的。
修改须要辣么多时间,不修改不就能够了吗?
差很少,是在没用到的时候不修改。
就像是过年各类亲戚给你压岁钱,而后都到了你家长手里,$\color{red}{你用的时候再给你}$。(而后这钱可能永远也到不了你手里了,qwq)。
实现方法:
1.用一个标记记录下这个增量。
2.当要修改的区间彻底包含当前区间就给当前区间的标记加上这个增量,再也不向下递归。
当须要查询子节点时怎么办?
用到一个下放操做。
1.当前节点的标记累加到子节点的标记中。
2.修改子节点状态。
3.该节点标记清$0$。
结构体:
struct node{ int w,l,r,lazy;//lazy就是这个标记。 }tree[MAXN*4+1];
下放操做:
inline void pushdown(int now){ if(tree[now].lazy){ tree[now<<1].lazy+=tree[now].lazy; tree[now<<1|1].lazy+=tree[now].lazy;//将lazy标记向下传. tree[now<<1].w+=(tree[now<<1].r-tree[now<<1].l+1)*tree[now].lazy; tree[now<<1|1].w+=(tree[now<<1|1].r-tree[now<<1|1].l+1)*tree[now].lazy;//更新区间和 tree[now].lazy=0;//清零 } }
区间修改:
void update_range(int x,int y,int c,int now){ if(tree[now].l>=x&&tree[now].r<=y){//当前区间包含于要修改的区间. tree[now].w+=(tree[now].r-tree[now].l+1)*c; tree[now].lazy+=c; return; } if(tree[now].lazy) pushdown(now);//下传 int mid=(tree[now].l+tree[now].r)>>1; if(x<=mid) update_range(x,y,c,now<<1); if(y>mid) update_range(x,y,c,now<<1|1); tree[now].w=tree[now<<1].w+tree[now<<1|1].w;//这几行和区间查询差很少 }
由于加入了lazy标记因此其余的操做也有改变。
单点查询:
int ask_single(int x,int now){ if(tree[now].l==tree[now].r){ return tree[now].w; } if(tree[now].lazy) pushdown(now);//要改的地方只有一个下传 int mid=(tree[now].l+tree[now].r)>>1; if(x<=mid) ask_single(x,now<<1); else ask_single(x,now<<1|1); }
区间查询:
void ask_range(int x,int y,int now){ if(tree[now].l>=x&&tree[now].r<=y){ ans+=tree[now].w; return; } if(tree[now].lazy) pushdown(now);//要改的地方只有一个下传 int mid=(tree[now].l+tree[now].r)>>1; if(x<=mid) ask_range(x,y,now<<1); if(y>mid) ask_range(x,y,now<<1|1); }
单点修改
void update_single(int x,int c,int now){// if(tree[now].l==tree[now].r){ tree[now].w+=c; return; } if(tree[now].lazy) pushdown(now);//要改的地方只有一个下传 int mid=(tree[now].l+tree[now].r)>>1; if(x<=mid) update_single(x,c,now<<1); else update_single(x,c,now<<1|1); tree[now].w=tree[now<<1].w+tree[now<<1|1].w; }
求出$[x,y]$区间每个数的和。
在查询的过程当中存在如下几种状况:
void ask_range(int x,int y,int now){ if(tree[now].l>=x&&tree[now].r<=y){//[l,r]是[x,y]的子集 ans+=tree[now].w; return;//不结束的话进行下面的会重复计算。 } int mid=(tree[now].l+tree[now].r)>>1; if(x<=mid){//要查询的区间在当前区间的左边 ask_range(x,y,now<<1); } if(y>mid){//要查询的区间在当前区间的右边 ask_range(x,y,now<<1|1); } }
#include<iostream> #include<cstring> #include<string> #include<cstdio> #include<algorithm> #define MAXN 500001 using namespace std; struct node{ int l,r,w; int lazy; }tree[MAXN<<2]; int ans; inline int read(){ int x=0;bool f=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=!f;c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();} return f?-x:x; } void build(int l,int r,int now){ tree[now].l=l,tree[now].r=r; tree[now].lazy=0; if(tree[now].l==tree[now].r){ tree[now].w=read(); return; } int mid=(tree[now].l+tree[now].r)>>1; build(l,mid,now<<1),build(mid+1,r,now<<1|1); tree[now].w=tree[now<<1].w+tree[now<<1|1].w; } inline void pushdown(int now){ tree[now<<1].lazy+=tree[now].lazy; tree[now<<1|1].lazy+=tree[now].lazy; tree[now<<1].w+=tree[now].lazy*(tree[now<<1].r-tree[now<<1].l+1); tree[now<<1|1].w+=tree[now].lazy*(tree[now<<1|1].r-tree[now<<1|1].l+1); tree[now].lazy=0; } void update_single(int x,int k,int now){//单点修改 if(tree[now].l==tree[now].r){ //tree[now].w=k;将x这个位置的数改成k //tree[now].w+=k;将x这个位置的数加上k return; } if(tree[now].lazy!=0) pushdown(now); int mid=(tree[now].l+tree[now].r)>>1; if(x<=mid) update_single(x,k,now<<1); else update_single(x,k,now<<1|1); tree[now].w=tree[now<<1].w+tree[now<<1|1].w; } int ask_single(int x,int now){//单点查询 if(tree[now].l==tree[now].r) return tree[now].w; if(tree[now].lazy!=0) pushdown(now); int mid=(tree[now].l+tree[now].r)>>1; if(x<=mid) return ask_single(x,now<<1); else return ask_single(x,now<<1|1); } void update_range(int x,int y,int k,int now){//区间修改 if(tree[now].l>=x&&tree[now].r<=y){ tree[now].w+=(tree[now].r-tree[now].l+1)*k; tree[now].lazy+=k; return; } if(tree[now].lazy!=0) pushdown(now); int mid=(tree[now].l+tree[now].r)>>1; if(x<=mid) update_range(x,y,k,now<<1); if(y>mid) update_range(x,y,k,now<<1|1); tree[now].w=tree[now<<1].w+tree[now<<1|1].w; } void ask_range(int x,int y,int now){ if(tree[now].l>=x&&tree[now].r<=y){ ans+=tree[now].w; return; } if(tree[now].lazy!=0) pushdown(now); int mid=(tree[now].l+tree[now].r)>>1; if(x<=mid) ask_range(x,y,now<<1); if(y>mid) ask_range(x,y,now<<1|1); } int main(){ return 0; }