◎引例◎
在详细解释之前,我们先来看一下洛谷上的两个线段树模板题目(洛谷P3372、P3373)。
从这几到题目可以看出,线段树的基本操作大致有三种:①给区间中的每一个元素加一个值 ②给区间中的每一个元素乘一个值 ③求出一个区间的每个元素和,下面我们先对线段树做一个基本的了解。
◎线段树的定义和结构◎
线段树的定义
线段树是一种二叉搜索树,时间复杂度为O(nlogn)。
线段树的结构
线段树,顾名思义,是树形结构,如下图所示:
线段树应用了二分的思想,“树根”带表总的区间,然后不断对区间二分,直到代表单个值。在上述两个例题中,线段树的节点值都代表了所覆盖区间的元素和,在一些其他情况下,这个节点值也可以代表其他的东西,例如:区间的最大值或者最小值等等。
◎线段树的建树◎
线段树的建树过程就是一个不断二分区间的过程(解释的通俗,但是可能不准确……)。从最大的区间开始,一步一步的二分,直到左右区间重合,也就是到了叶子结点,就开始给线段树赋值,下面附建树代码(本文的代码都是上文例题中的节选,在不同的题目中,可能有所不同)
void build(int l,int r,int u){ //[l,r]区间,u节点 tree[u].l=l,tree[u].r=r; if(tree[u].l==tree[u].r){ //[l,r]区间只有一个数(到达低端) scanf("%lld",&tree[u].w); return; } int mid=(l+r)>>1; build(l,mid,u<<1); //建立左子树 build(mid+1,r,u<<1|1); //建立右子树 tree[u].w=tree[u+u].w+tree[u+u+1].w; //根节点=左子树+右子树 }
◎线段树的区间修改◎
区间修改可以说是线段树最难理解的一部分,因为在这里有线段树特有的标记,名叫——懒惰标记(lazytag)。这个标记的大致作用就是在修改的时候,只修改当前节点,而不是全部,这也是线段树时间复杂度低的原因,而这就用到了lazytag,用来记录修改操作。然后,下一次修改时,如果当前的标记影响到了修改,就将标记下推。
◎线段树的区间查询◎
在熟练区间修改之后,查询就相对来说简单了,依旧是应用了二分的思想。
◎例题代码◎
例题(一)
#include<bits/stdc++.h>
#define MAXN 100007
using namespace std; int n,m,x,y,k,q; long long ans; struct Tree{ int l; int r; //[l,r]区间
long long w; //当前区间的值
int lazytag; }tree[MAXN<<2]; //MAXN*2^2
inline void build(int l,int r,int u){ //[l,r]区间,u节点
tree[u].l=l,tree[u].r=r; if(tree[u].l==tree[u].r){ //[l,r]区间只有一个数(到达低端)
scanf("%lld",&tree[u].w); return; } int mid=(l+r)>>1; build(l,mid,u+u); //建立左子树
build(mid+1,r,u+u+1); //建立右子树
tree[u].w=tree[u+u].w+tree[u+u+1].w; //根节点=左子树+右子树
} inline void down(int u){ tree[u+u].lazytag+=tree[u].lazytag; tree[u+u+1].lazytag+=tree[u].lazytag; //lazytag下推
tree[u+u].w+=(tree[u+u].r-tree[u+u].l+1)*tree[u].lazytag; tree[u+u+1].w+=(tree[u+u+1].r-tree[u+u+1].l+1)*tree[u].lazytag; //重置区间值
tree[u].lazytag=0; //删除lazytag
} inline void change(int u){ if(tree[u].l>=x && tree[u].r<=y){ //--->[x,y]区间包含[l,r]区间
tree[u].w+=(tree[u].r-tree[u].l+1)*k; //[l,r]的和加个数*k
tree[u].lazytag+=k; //lazytag----该节点以下的叶节点+k
return; } if(tree[u].lazytag) down(u); //有lazytag就下推
int mid=(tree[u].l+tree[u].r)>>1; if(x<=mid) change(u+u); //修改左区间
if(mid<y) change(u+u+1); //修改右区间
tree[u].w=tree[u+u].w+tree[u+u+1].w; //重置节点值
} inline void ask(int u){ if(tree[u].l>=x && tree[u].r<=y){ //--->[x,y]区间包含[l,r]区间
ans+=tree[u].w; return; } if(tree[u].lazytag) down(u); int mid=(tree[u].l+tree[u].r)>>1; if(x<=mid) ask(u+u); if(y>mid) ask(u+u+1); } int main(){ scanf("%d%d",&n,&m); build(1,n,1); for(int i=1;i<=m;i++){ scanf("%d",&q); if(q==1){ scanf("%d%d%d",&x,&y,&k); change(1); } else{ ans=0; scanf("%d%d",&x,&y); ask(1); printf("%lld\n",ans); } } return 0; }
例题(二)
#include <iostream> #include <cstdio> #include <cctype> #define R register int #define ls u << 1 #define rs u << 1 | 1 #define mid ((t[u].l + t[u].r) >> 1) #define zq l, mid #define yq mid + 1, r #define len(L) t[(L)].r - t[(L)].l + 1 const int M = 500500; typedef long long ll; ll n, m, p, ans; ll opt, x, y, k; struct Tree{ int l, r; ll w, ad, mu; Tree(){ l = r = w = ad = 0; mu = 1; } void clear(){ ad = 0; mu = 1; } void color(ll add, ll mul) { ad = (ad * mul + add) % p; mu = mu * mul % p; w = (w * mul + (r - l + 1) * add) % p; } }t[M << 1]; inline void read(ll &now) { now = 0; char c = getchar(); while (!isdigit(c)) c = getchar(); while (isdigit(c)) { now = now * 10 + (c ^ 48); c = getchar(); } } inline void down(int u) { t[ls].color(t[u].ad, t[u].mu); t[rs].color(t[u].ad, t[u].mu); t[u].clear(); } inline void update(int u) { t[u].w = (t[ls].w + t[rs].w) % p; } void build(int l, int r, int u) { t[u].l = l, t[u].r = r; if (l == r) { read(t[u].w); t[u].w %= p; return; } build(zq, ls); build(yq, rs); update(u); } void change(int u, int mul, int add) { if (t[u].l >= x && t[u].r <= y) { t[u].color(add, mul); return; } if (t[u].ad || t[u].mu != 1) down(u); if (mid >= x) change(ls, mul, add); if (mid < y) change(rs, mul, add); update(u); } void query(int u) { if (t[u].l >= x && t[u].r <= y) { ans = (ans + t[u].w) % p; return; } if (t[u].ad || t[u].mu != 1) down(u); if (mid >= x) query(ls); if (mid < y) query(rs); } int main() { read(n), read(m), read(p); build(1, n, 1); for (R i = 1; i <= m; ++i) { read(opt); if (opt == 1) { read(x), read(y), read(k); change(1, k, 0); } else if (opt == 2) { read(x), read(y), read(k); change(1, 1, k); } else { read(x), read(y); ans = 0; query(1); printf("%lld\n", ans); } } return 0; }