洛谷题目链接:线段树
线段树是一种用于区间修改查询的数据结构,可以支持的操作有单点修改区间查询,区间修改单点查询,区间修改区间查询等.
线段树有递归版和结构体版,递归版在处理一开始没有赋初始值的问题时可以不用建树,而结构体版的则显得比较条理清晰.
线段树比树状数组的代码复杂的多,但是树状数组多支持一个区间修改区间查询的操作,并且可以与其他的一些数据结构相适应(像是树链剖分等),也有很多的在此基础上的提高的算法,线面开始讲解一下思路.
线段树操作如下:
- 输入点权,建树.
- 进行修改和查询.
建树:
在线段树中建树一般采用的是递归的方式建树,在建树的过程中保留每个点的信息(区间左端点,右端点,区间和等),在线段树中的每个节点就是一个区间.
代码如下:
void build(lol root,lol left,lol right){//节点,节点左端点,节点右端点 if(left==right){//当节点左端点等于右端点时,即为叶子节点 sum[root]=w[left];//叶子节点的区间和即为点权 return;//这里记得要退出回溯 } build(root*2,left,mid);//左子树 build(root*2+1,mid+1,right);//右子树递归建树 sum[root]=sum[ll(root)]+sum[rr(root)];//回溯时收集区间和 }
那么这样建好的树就会有这样的性质:
- 当前节点的左右儿子分别是root*2和root*2+1
- 当前节点包括的范围刚好是左右儿子的区间的并集
所以之后在进行修改,查询等操作时会很方便.
下移懒惰标记:
这里提及了一个重要的操作:lazy[]数组,懒惰标记.
lazy数组用于保存修改在某个区间上的值,但不即时修改赋值到节点上,而是在之后查询时需要查询它子树的值的时候再将懒惰标记下移.
那么下移lazy标记就是将当前区间的修改细化到每个小区间上.具体细节见代码注释:
void pushdown(int root,int left,int right){ lazy[root*2]+=lazy[root];//左右子树加上上面节点的标记 lazy[root*2+1]+=lazy[root]; sum[root*2]+=lazy[root]*(mid-left+1);//将区间和加上子树的个数乘标记的大小 sum[root*2+1]+=lazy[root]*(right-mid); lazy[root]=0;//消去节点的懒惰标记 }
为什么修改区间和时要将左子树乘上(mid-left+1)呢?因为左子树的左端点是left,右端点是mid,那么正好(mid-left+1)就是左子树的节点数,乘上lazy[root]的值也就是将它的子树每个加上lazy[root]的值.
修改:
修改时是寻找一个能全部包含于修改范围的区间,并将它打上lazy标记.
一个需要修改的范围可以看做是一个个小的范围的集合,如:
1到8的区间可以看做是[1,5]和[6,8];5到6的区间可以看做是[5,5]和[6,6];
如果是将1~8加5,则将区间[1,5]和区间[6,8]节点的懒惰标记加上5就可以了.
代码如下:
1 void updata(int root,int left,int right,int l,int r,int val){ 2 if(l<=left&&right<=r){//如果找到一个能全部被包含于修改范围的区间 3 lazy[root]+=val;//则加上懒惰标记 4 sum[root]+=val*(right-left+1);//同时也要修改区间和,与pushdown的修改同理 5 return; 6 } 7 if(lazy[root]) pushdown(root,left,right);//修改时也要将之前的懒惰标记下移 8 if(l<=mid) updata(ll(root),left,mid,l,r,val); 9 if(mid<r) updata(rr(root),mid+1,right,l,r,val);//递归寻找能全被包含的区间 10 sum[root]=sum[ll(root)]+sum[rr(root)]; 11 }
查询:
查询和修改操作比较像,也是将一个大区间细化为一个个小区间,找能被完全包含的区间进行查询.
下面直接上代码:
1 lol query(int root,int left,int right,int l,int r){ 2 if(l<=left&&right<=r) return sum[root];//完全被包含的区间直接返回区间和 3 if(r<left||right<l) return 0;//如果区间和查找区间没有交集,则直接返回0 4 if(lazy[root]) pushdown(root,left,right);//这里要写在判断是否与查找区间有交集后面 5 return query(ll(root),left,mid,l,r)+query(rr(root),mid+1,right,l,r);//递归查询 6 }
为什么要把懒惰标记下移的操作放在判断后呢?我们举个例子,假设已经递归到了叶子节点,如果先下移标记,就有可能导致数组的越界(向下移标记时左右子树的节点标号都是节点的两倍),所以要先进行判断区间的操作.
几个简单的操作讲完了,下面放一个完整模板:
1 #include<bits/stdc++.h> 2 #define mid (left+right>>1) 3 #define ll(x) (x<<1) 4 #define rr(x) (x<<1|1) 5 using namespace std; 6 typedef long long lol; 7 const int N=500000; 8 9 lol n,m; 10 lol w[N+10]; 11 lol sum[N+10]; 12 lol lazy[N+10]; 13 14 int gi(){ 15 int ans=0,f=1;char i=getchar(); 16 while(i<'0'||i>'9'){if(i=='-')f=-1;i=getchar();} 17 while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();} 18 return ans*f; 19 } 20 21 void build(lol root,lol left,lol right){ 22 if(left==right){ 23 sum[root]=w[left]; 24 return; 25 } 26 build(ll(root),left,mid); 27 build(rr(root),mid+1,right); 28 sum[root]=sum[ll(root)]+sum[rr(root)]; 29 } 30 31 void pushdown(int root,int left,int right){ 32 lazy[ll(root)]+=lazy[root]; 33 lazy[rr(root)]+=lazy[root]; 34 sum[ll(root)]+=lazy[root]*(mid-left+1); 35 sum[rr(root)]+=lazy[root]*(right-mid); 36 lazy[root]=0; 37 } 38 39 void updata(int root,int left,int right,int l,int r,int val){ 40 if(l<=left&&right<=r){ 41 lazy[root]+=val; 42 sum[root]+=val*(right-left+1); 43 return; 44 } 45 if(lazy[root]) pushdown(root,left,right); 46 if(l<=mid) updata(ll(root),left,mid,l,r,val); 47 if(mid<r) updata(rr(root),mid+1,right,l,r,val); 48 sum[root]=sum[ll(root)]+sum[rr(root)]; 49 } 50 51 lol query(int root,int left,int right,int l,int r){ 52 if(l<=left&&right<=r) return sum[root]; 53 if(r<left||right<l) return 0; 54 if(lazy[root]) pushdown(root,left,right); 55 return query(ll(root),left,mid,l,r)+query(rr(root),mid+1,right,l,r); 56 } 57 58 int main(){ 59 int x,y,val,flag; 60 n=gi();m=gi(); 61 for(int i=1;i<=n;i++) w[i]=gi(); 62 build(1,1,n); 63 for(int i=1;i<=m;i++){ 64 flag=gi(); 65 if(flag==1){ 66 x=gi();y=gi();val=gi(); 67 updata(1,1,n,x,y,val); 68 } 69 if(flag==2){ 70 x=gi();y=gi(); 71 printf("%lld ",query(1,1,n,x,y)); 72 } 73 } 74 return 0; 75 }
另外再贴一个结构体版的:
#include<bits/stdc++.h> #define ll(x) (x<<1) #define rr(x) (x<<1|1) using namespace std; const int N=400000+5; typedef long long lol; lol n, m; lol w[N]; struct seg_tree{ lol sum, l, r, lazy; }t[N]; lol gi(){ lol ans = 0 , f = 1; char i=getchar(); while(i<'0'||i>'9'){if(i=='-')f=-1;i=getchar();} while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();} return ans * f; } void up(lol root){ t[root].sum = t[ll(root)].sum + t[rr(root)].sum; } void build(lol root,lol l,lol r){ int mid = l+r>>1; t[root].l = l , t[root].r = r; if(l == r){ t[root].sum = w[l]; return; } build(ll(root),l,mid); build(rr(root),mid+1,r); up(root); } void pushdown(lol root){ lol mid = t[root].l + t[root].r >> 1; t[ll(root)].lazy += t[root].lazy; t[rr(root)].lazy += t[root].lazy; t[ll(root)].sum += t[root].lazy*(mid-t[root].l+1); t[rr(root)].sum += t[root].lazy*(t[root].r-mid); t[root].lazy = 0; } void updata(lol root,lol l,lol r,lol val){ lol mid = t[root].l+t[root].r>>1; if(l<=t[root].l && t[root].r<=r){ t[root].sum += val * (t[root].r-t[root].l+1); t[root].lazy += val; return; } if(t[root].lazy) pushdown(root); if(l <= mid) updata(ll(root),l,r,val); if(mid < r) updata(rr(root),l,r,val); up(root); } lol query(lol root,lol l,lol r){ if(l<=t[root].l && t[root].r<=r) return t[root].sum; if(r<t[root].l || t[root].r<l) return 0; if(t[root].lazy) pushdown(root); return query(ll(root),l,r)+query(rr(root),l,r); } int main(){ lol f, x, y, val; n = gi(); m = gi(); for(lol i=1;i<=n;i++) w[i] = gi(); build(1,1,n); for(lol i=1;i<=m;i++){ f = gi(); x = gi(); y = gi(); if(f == 1) val = gi() , updata(1,x,y,val); else printf("%lld ",query(1,x,y)); } return 0; }