数据结构——线段树
Posted ackers
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构——线段树相关的知识,希望对你有一定的参考价值。
线段树是一种基于分治思想的类似于二叉树的数据结构,一般用于数组的信息统计,相比于树状数组,线段树有着更广阔的应用空间,但是相对的其代码量长,且常数大
一.
首先我们来讲线段树的建树过程,请看下图:
这张图就是线段树的存储结构,我们从最长的区间开始依次分成两部分,每一部分都有一个需要维护的权,建树过程比较简单,代码如下:
inline void build(int l,int r,int rt) //l表示当前的左端点,r表示右端点,rt是当前区间的编号 { if(l == r) //当左右端点相同,说明当前的区间是一个点,给予该点一个初值 { scanf("%d",c[rt]); return ; } int m = (l + r) >> 1; build(l,m,rt<<1); build(m + 1,r,rt<<1|1); //分成两个区间继续进行建树操作 update(rt); //更新当前区间的值 return ; } build(1,n,1);
关于上面的update函数,一般是根据你需要维护的条件进行的,下面给出几个例子:
inline void update(int rt) { c[rt] = c[rt<<1] + c[rt<<1|1]; //维护区间和 c[rt] = std::max(c[rt<<1],c[rt<<1|1]);//维护区间最大值 c[rt] = std::min(c[rt<<1],c[rt<<1|1]);//维护区间最小值 return ; }
现在建树过程大概都了解了,我们开始讲查询和修改操作
首先我们先讲单点修改,具体操作就是从根节点开始,每次比较我们插入的位置与中点位置的大小,从而选择左右区间,做法类似于二分,在这里不多讲,代码如下:
inline void modify(int l,int r,int rt,int x,int y) //给x加上y { if(l == r) { c[rt] += y; return ; } int m = (l + r) >> 1; if(x <= m) modify(l,m,rt<<1,x,y); else modify(m + 1,r,rt<<1|1,x,y); update(rt); return ; } modify(1,n,1,x,y);
单点查询操作的实现在做法上与单点修改一样,代码如下:
inline void query(int l,int r,int rt,int p) //p为我们需要查询的点 { if(l == r && l == p) { ans = c[rt]; return ; } int m = (l + r) >> 1; if(p <= m) query(l,m,rt<<1,p); else query(m + 1,r,rt<<1|1,p); return ; } query(1,n,1,p);
然后我们考虑区间查询操作,我们可以发现,当我们要查询的区间的左端点小于等于中点时,那么它与左区间一定有交集,我们就可以进入左子树查询,相应的,当它的右端点大于中点时,与右区间有交集,我们进入右子树查询,并且当我们当前的区间左端点大于等于查询区间左端点,且右端点小于等于查询区间右端点时,那么这个区间一定在查询区间内,我们就直接用该区间权值维护答案,不必再向下搜索,于是我们得到区间查询的方法,代码如下:
inline void query(int l,int r,int rt,int nl,int nr) //nl,nr为当前需要查询的区间 { if(nl <= l &&r <= nr) { ans += c[rt]; //这里我们用求区间和举例 return ; } int m = (l + r) >> 1; if(nl <= m) query(l,m,rt<<1,nl,nr); if(nr > m) query(m + 1,r,rt<<1|1,nl,nr); return ; } query(1,n,1,nl,nr);
二.
关于区间修改,一个显然的想法是,我们像区间查询那样做,但是在这里有个问题,我们在修改区间的时候,如果整个区间都被覆盖,那么每一个节点都需要更新,复杂度会上升到O(n),因此,我们来考虑如何进行优化,在这里,我们就引入延迟标记,当一个区间l ~ r被修改后,我们需在[l ~ r]这个区间加上一个 延迟标记,当我们在查询的时候,如果当前区间的若干个子树需要修改或查询,我们就检验当前区间是否有延迟标记,然后下发标记,这时我们发现,区间修改的复杂度会降至O(nlogn)
下面给出区间修改和加法延迟标记的实现(这里以区间查询为例):
inline void sign(int l,int r,int rt,int v) //延迟标记 { c[rt] += (r - l + 1)*v; sg[rt] += v; return ; } inline void push_down(int l,int r,int rt) //下放标记 { if(sg[rt]) { int m = (l + r) >> 1; sign(l,m,sg[rt]); sign(m + 1,r,sg[rt]); sg[rt] = 0; } return ; } inline void modify(int l,int r,int rt,int nl,int nr,int v) { if(nl <= l && r <= nr) { sign(l,r,rt,v); return ; } push_down(l,r,rt); int m = (l + r) >> 1; if(nl <= m) modify(l,m,rt<<1,nl,nr,v); if(nr > m) query(m + 1,r,rt<<1|1,nl,nr,v); return ; } inline void query(int l,int r,int rt,int nl,int nr) { if(nl<=l&&r<=nr) { ans += c[rt]; return ; } push_down(l,r,rt); int m = (l + r) >> 1; if(nl <= m) query(l,m,rt<<1,nl,nr); if(nr > m) query(m + 1,r,rt<<1|1,nl,nr); return ; } modify(1,n,1,nl,nr,v); query(1,n,1,nl,nr);
值得注意的是,在复杂的线段树操作中可能会出现多种延迟标记,这时需要我们考虑运算顺序,如一个线段树中同时存在着加法标记和乘法标记,运算的顺序不同,结果就可能会不同
关于延迟标记这里给出两道例题:
这两道例题都非常的简单,在这里我给出第一个例题的代码,第二个例题可以参照第一题的做法
#include <cstdio> #include <cstring> #include <algorithm> #define root 1,n,1 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define mid (l+r)>>1 const int maxn = 1e6 + 6; int n,tm; long long c[maxn]; int sg[maxn]; long long ans; inline int read() { char ch = getchar();int x = 0,f = 1; while(ch<‘0‘||ch>‘9‘){if(ch == ‘-‘) f = -1;ch = getchar();} while(ch>=‘0‘&&ch<=‘9‘){x = x*10 + ch -‘0‘;ch = getchar();} return x*f; } inline void update(int rt) { c[rt] = c[rt<<1] + c[rt<<1|1]; return ; } inline void sign(int l,int r,int rt,int v) { c[rt] += 1ll*(r-l+1)*v; sg[rt] += v; return ; } inline void push_down(int l,int r,int rt) { if(sg[rt]) { int m = mid; sign(lson,sg[rt]); sign(rson,sg[rt]); sg[rt] = 0; } return ; } inline void build (int l,int r,int rt) { if(l==r) { c[rt] = read(); return ; } int m = mid; build(lson); build(rson); update(rt); } inline void modify(int l,int r,int rt,int nl,int nr,int v) { if(nl<=l&&r<=nr) { sign(l,r,rt,v); return ; } push_down(l,r,rt); int m = mid; if(nl<=m) modify(lson,nl,nr,v); if(nr>m) modify(rson,nl,nr,v); update(rt); } inline void query(int l,int r,int rt,int nl,int nr) { if(nl<=l&&r<=nr) { ans += c[rt]; return ; } push_down(l,r,rt); int m = mid; if(nl<=m) query(lson,nl,nr); if(nr>m) query(rson,nl,nr); update(rt); } int main(int argc, char const *argv[]) { n = read(); tm = read(); build(root); for(int i = 1;i <= tm;i ++) { int flag,x,y,k; flag = read(); if(flag == 1) { x = read(); y = read(); k = read(); modify(root,x,y,k); } else { ans = 0; x = read(); y = read(); query(root,x,y); printf("%lld ",ans); } } return 0; }
以上是关于数据结构——线段树的主要内容,如果未能解决你的问题,请参考以下文章