线段树是一种非常重要的数据结构,主要用动态维护区间信息。
线段树是的功能是把一个区间划分成许多个小区间,每个长度大于1的区间被从中间分成两段,分别对应该节点的左子树和右子
树,如图:
所以可以看出,假设一个节点对应的区间为 [l,r], 记mid=(l+r)/2,则其左子树对应区间为 [l,mid],右子树对应区间为[mid+1,r]。
复杂度方面,线段树所需要的数组大小是数列长度的4倍,时间复杂度为O(NlogN)。
线段树的基本操作
1.建树
建树的过程就是从根节点开始,递归建立左右子树。如果是叶子节点,赋值并回溯即可。注意minv数组初始值应为一个极大的数
int maxv[maxn*4],minv[maxn*4],sum[maxn*4]; int a[maxn];//原始数列 void build(int root,int l,int r) { if(l==r) maxv[root]=minv[root]=sum[root]=a[l];//叶子节点 else{ int mid=(l+r)/2; build(root*2,l,mid); build(root*2+1,mid+1,r); maxv[root]=max(maxv[root*2],maxv[root*2+1]); minv[root]=min(minv[root*2],minv[root*2+1]); sum[root]=sum[root*2]+sum[root*2+1]; } }
2.区间查询
从根节点开始递归,如果查询区间完全包含在当前节点的区间中,直接返回当前节点的值即可。如果查询区间和左子树有交集,递归左子树,和右子树有交集就递归右子树。
代码(以求最大值为例)
int query(int root,int ql,int qr,int l,int r) { int ans=0; if(ql<=l&&qr>=r) return maxv[root]; int mid=(l+r)/2; if(ql<=mid) ans=max(ans,query(root*2,ql,qr,l,mid)); if(qr>mid) ans=max(ans,query(root*2+1,ql,qr,mid+1,r)); return ans; }
注意求最小值时ans的初值
3.单点修改
从根节点开始递归,判断修改的位置在当前节点的左子树还是右子树,递归相应的子树,然后更新当前节点的值。
void update(int root,int l,int r,int pos,int val) { if(l==r){ maxv[root]=val; return; } int mid=(l+r)/2; if(pos<=mid) update(root*2,l,mid,pos,val); else update(root*2+1,mid+1,r,pos,val); maxv[root]=max(maxv[root*2],maxv[root*2+1]); }
4.区间修改
线段树最重要的功能。我们不可能对区间中的每个数做单点修改,时间复杂度将退化为N^2logN,无法接受。
所以这里引入线段树的核心操作,lazytag。
对于每一个节点,我们加一个变量,记录该点进行的修改操作,称为懒标记,父节点的修改操作会对子节点产生影响。
每次递归访问子树之前,都要进行一次维护,将该节点的lazytag值传给子节点,并相应地修改子节点的值,最后清空该节点的lazytag。
代码(以加法为例,维护区间和):
struct node { int sum; int tag; }tr[maxn*4];
void pushdown(int root,int l,int r) { if(tr[root].tag){ int mid=(l+r)/2; tr[root*2].tag+=tr[root].tag; tr[root*2+1].tag+=tr[root].tag; tr[root*2].sum+=tr[root].tag*(mid-l+1); tr[root*2+1].sum+=tr[root].tag*(r-mid); tr[root].tag=0; } }
所以区间修改的代码就很简单了,从根节点开始递归,如果当前节点完全包含在修改区间内,修改当前节点的lazytag和值然后回溯即可,否则先执行一次pushdown操作,再判断是否包含左子树,右子树,相应递归即可。
void update(int root,int ul,int ur,int l,int r,add) { if(ul<=l&&ur>=r){ tr[root].tag+=add; tr[root].val+=add*(r-l+1); return; } pushdown(root,l,r); int mid=(l+r)/2; if(ul<=mid) update(root*2,ul,ur,l,mid,add); if(ur>mid) update(root*2+1,ul,ur,mid+1,r,add); tr[root].val=tr[root*2].val+tr[root*2+1].val; }
例题:
Luogu3372,3373(Luogu2023,BZOJ1798) 区间修改,区间查询
Luogu1198(BZOJ1012),Luogu1531 单点修改,区间最值
Luogu1816 RMQ问题(可ST表)