线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。
一.建树:
例如:一个长度为4的线段,我们可以表示成这样:
如果你要表示线段的和,那么最上面的根节点的权值表示的是这个线段1~4的和。根的两个儿子分别表示这个线段中1~2的和,与3~4的和。以此类推。
然后我们还可以的到一个性质:节点i的权值=她的左儿子权值+她的右儿子权值。因为1~4的和就是等于1~2的和+3~4的和。
根据这个思路,我们就可以建树了,设一个结构体tree,tree[i].l和tree[i].r分别表示这个点代表的线段的左右下标,tree[i].sum表示这个节点表示的线段和。
我们知道,一颗二叉树,她的左儿子和右儿子编号分别是她*2和她*2+1,得到式子:tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;
建树代码:
1 inline void build(int i,int l,int r){//递归建树 2 tree[i].l=l;tree[i].r=r; 3 if(l==r){//如果这个节点是叶子节点 4 tree[i].sum=input[l]; 5 return ; 6 } 7 int mid=(l+r)>>1; 8 build(i*2,l,mid);//分别构造左子树和右子树 9 build(i*2+1,mid+1,r); 10 tree[i].sum=tree[i*2].sum+tree[i*2+1].sum; 11 return ; 12 }
二.修改:
单点修改:
从根节点开始,在区间的dis位上+/-/*/除k,原路返回时将左右节点的值加入父节点完成修改;
代码(加法):
1 inline void add(int i,int dis,int k){ 2 if(tree[i].l==tree[i].r){//此时说明找到了dis位 3 tree[i].sum+=k; 4 return ; 5 } 6 if(dis<=tree[i*2].r) add(i*2,dis,k);//在哪往哪跑 7 else add(i*2+1,dis,k); 8 tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;//返回更新 9 return ; 10 }
区间修改:
此时需要记录一个“懒标记”lazytage,来记录这个区间,以便在查询需要的时候可以传递下去,减少时间
区间修改的时候,我们按照如下原则:
1、如果当前区间被完全覆盖在目标区间里,讲这个区间的sum+k*(tree[i].r-tree[i].l+1);
2、如果没有完全覆盖,则先下传懒标记;
3、如果这个区间的左儿子和目标区间有交集,那么搜索左儿子;
4、如果这个区间的右儿子和目标区间有交集,那么搜索右儿子。
push_down代码:
1 void push_down(int i) 2 { 3 if(tree[i].lz!=0) 4 { 5 tree[i*2].lz+=tree[i].lz;//左右儿子分别加上父亲的lz 6 tree[i*2+1].lz+=tree[i].lz; 7 init mid=(tree[i].l+tree[i].r)/2; 8 tree[i*2].data+=tree[i].lz*(mid-tree[i*2].l+1);//左右分别求和加起来 9 tree[i*2+1].data+=tree[i].lz*(tree[i*2+1].r-mid); 10 tree[i].lz=0;//父亲lz归零 11 } 12 return ; 13 }
区间修改代码:
1 void add(int i,int l,int r,int k) 2 { 3 if(tree[i].r<=r && tree[i].l>=l)//如果当前区间被完全覆盖在目标区间里,将这个区间的sum+k*(tree[i].r-tree[i].l+1) 4 { 5 tree[i].sum+=k*(tree[i].r-tree[i].l+1); 6 tree[i].lz+=k;//记录lazytage 7 return ; 8 } 9 push_down(i);//向下传递 10 if(tree[i*2].r>=l) 11 add(i*2,l,r,k); 12 if(tree[i*2+1].l<=r) 13 add(i*2+1,l,r,k); 14 tree[i].sum=tree[i*2].sum+tree[i*2+1].sum; 15 return ; 16 }
三.查询:
单点查询:
1 void search(int i,int dis){ 2 ans+=tree[i].num;//一路加起来 3 if(tree[i].l==tree[i].r) 4 return ; 5 push_down(i);//若无区间修改这步可删 6 if(dis<=tree[i*2].r) 7 search(i*2,dis); 8 if(dis>=tree[i*2+1].l) 9 search(i*2+1,dis); 10 }
区间查询:
线段树的查询方法:
1、如果这个区间被完全包括在目标区间里面,直接返回这个区间的值;
2、如果没有完全覆盖,则先下传懒标记;
3、如果这个区间的左儿子和目标区间有交集,那么搜索左儿子;
4、如果这个区间的右儿子和目标区间有交集,那么搜索右儿子。
区间查询代码:
inline int search(int i,int l,int r){ if(tree[i].l>=l && tree[i].r<=r)//如果这个区间被完全包括在目标区间里面,直接返回这个区间的值 return tree[i].sum; if(tree[i].r<l || tree[i].l>r) return 0;//如果这个区间和目标区间毫不相干,返回0 push_down(i);//若无区间修改这步可删 int s=0; if(tree[i*2].r>=l) s+=search(i*2,l,r);//如果这个区间的左儿子和目标区间又交集,那么搜索左儿子 if(tree[i*2+1].l<=r) s+=search(i*2+1,l,r);//如果这个区间的右儿子和目标区间又交集,那么搜索右儿子 return s; }
四.乘法/除法/根号线段树(以后补)
五.模拟题及代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=5e7+20; 4 struct node 5 { 6 int r,l,sum; 7 }tree[maxn]; 8 int input[maxn]; 9 void build(int i,int l,int r) 10 { 11 tree[i].l=l;tree[i].r=r; 12 if(l==r){ 13 tree[i].sum=input[l];return ; 14 } 15 int mid=(l+r)>>1; 16 build(i*2,l,mid); 17 build(i*2+1,mid+1,r); 18 tree[i].sum=tree[i*2].sum+tree[i*2+1].sum; 19 } 20 int search(int i,int l,int r) 21 { 22 if(tree[i].l>=l&&tree[i].r<=r)return tree[i].sum; 23 if(tree[i].r<l||tree[i].l>r)return 0; 24 int s=0; 25 if(tree[i*2].r>=l)s+=search(i*2,l,r); 26 if(tree[i*2+1].l<=r)s+=search(i*2+1,l,r); 27 return s; 28 } 29 void add(int i,int dis,int k) 30 { 31 if(tree[i].l==tree[i].r){ 32 tree[i].sum+=k; 33 return ; 34 } 35 if(dis<=tree[i*2].r)add(i*2,dis,k); 36 else add(i*2+1,dis,k); 37 tree[i].sum=tree[i*2].sum+tree[i*2+1].sum; 38 return ; 39 } 40 int n,m,book,x,y,k; 41 int main() 42 { 43 scanf("%d%d",&n,&m); 44 for(int i=1;i<=n;i++){ 45 scanf("%d",&input[i]); 46 } 47 build(1,1,n); 48 for(int i=1;i<=m;i++){ 49 scanf("%d",&book); 50 if(book==1){ 51 scanf("%d%d",&x,&k); 52 add(1,x,k); 53 } 54 else if(book==2){ 55 scanf("%d%d",&x,&y); 56 printf("%d ",search(1,x,y)); 57 } 58 } 59 return 0; 60 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 #define LL long long 3 using namespace std; 4 const int maxn=1e7+20; 5 struct node 6 { 7 LL r,l,sum,lz; 8 }tree[maxn]; 9 int input[maxn]; 10 void build(LL i,LL l,LL r) 11 { 12 tree[i].l=l,tree[i].r=r; 13 if(l==r){ 14 tree[i].sum=input[l];return ; 15 } 16 LL mid=(l+r)>>1; 17 build(i*2,l,mid); 18 build(i*2+1,mid+1,r); 19 tree[i].sum=tree[i*2].sum+tree[i*2+1].sum; 20 } 21 void push_down(LL i) 22 { 23 if(tree[i].lz){ 24 tree[i*2].lz+=tree[i].lz,tree[i*2+1].lz+=tree[i].lz; 25 LL mid=(tree[i].l+tree[i].r)/2; 26 tree[i*2].sum+=tree[i].lz*(mid-tree[i*2].l+1); 27 tree[i*2+1].sum+=tree[i].lz*(tree[i*2+1].r-mid); 28 tree[i].lz=0; 29 } 30 return ; 31 } 32 LL search(LL i,LL l,LL r) 33 { 34 if(tree[i].l>=l&&tree[i].r<=r)return tree[i].sum; 35 push_down(i); 36 LL s=0; 37 if(tree[i*2].r>=l)s+=search(i*2,l,r); 38 if(tree[i*2+1].l<=r)s+=search(i*2+1,l,r); 39 return s; 40 } 41 void add(LL i,LL l,LL r,LL k) 42 { 43 if(tree[i].l>=l&&tree[i].r<=r){ 44 tree[i].sum+=k*(tree[i].r-tree[i].l+1); 45 tree[i].lz+=k; 46 return ; 47 } 48 push_down(i); 49 if(tree[i*2].r>=l)add(i*2,l,r,k); 50 if(tree[i*2+1].l<=r) add(i*2+1,l,r,k); 51 tree[i].sum=tree[i*2].sum+tree[i*2+1].sum; 52 return ; 53 } 54 LL n,m,book,x,y,k; 55 int main() 56 { 57 scanf("%lld%lld",&n,&m); 58 for(int i=1;i<=n;i++){ 59 scanf("%lld",&input[i]); 60 } 61 build(1,1,n); 62 for(int i=1;i<=m;i++){ 63 scanf("%lld",&book); 64 if(book==1){ 65 scanf("%lld%lld%lld",&x,&y,&k); 66 add(1,x,y,k); 67 } 68 else if(book==2){ 69 scanf("%lld%d",&x,&y); 70 printf("%lld ",search(1,x,y)); 71 } 72 } 73 return 0; 74 }