线段树
线段树
概念
线段树是用来解决区间修改,区间查询的问题,它的修改与求和都是\(O(log_2n)\)的,效率非常高。它与前缀和的区别是能够修改数组中的元素。
基本思想
线段树是一颗二叉树,其每一个节点都对应序列的一段区间。如下图所示:
可以发现根节点对应的是整个区间\([1,8]\)。若一个节点对应的区间为\([l,r]\),当\(l=r\)时,该节点为叶子节点。否则令\(mid=\cfrac{l+r}{2}\),其左右儿子分别是\([l,mid]\)和\([mid+1,r]\)。显然,二叉树高度是\(log_2n\)级别的,所以时间复杂度也是\(O(log_2n)\)。
算法实现
建树
由于线段树是一个二叉树结构,我们采用递归来建树。我们令\(build(k,l,r)\)表示当前构建编号为\(k\)的\([l,r]\)区间。若\(l=r\),则该节点为叶子节点,赋值为初始值。否则,我们我们构造它的左右子树 \(build(k\times 2,l,mid)\) 与 \(build(k\times 2+1,mid+1,r)\),并合并两个子节点的答案。
区间询问
如下图,若要询问区间\([1,7]\),我们需要知道\([1,4]\),\([5,6]\),\([7,7]\)三个节点的信息:
令待查询区间为\([x,y]\)当前访问到了编号为\(k\)的区间\([l,r]\),这时有三种情况:
- 若待区间\(k\)与待查询区间完全无交集(\(y < l\) 或 \(x>r\)),该节点对答案没有贡献。
- 若待区间\(k\)是带查询区间的子集(\(x<=l\) 且 \(r<=y\)),直接返回该节点的答案。
- 除上述两种情况外,继续递归左右子树统计答案。
单点修改
若要修改节点\([1,1]\)的值,我们只需要修改包含它的节点(它和它的所有祖先节点)\([1,1]\),\([1,2]\),\([1,4]\),\([1,8]\)。
此时也有三种情况:
- 若待区间\(k\)与待查询区间完全无交集(\(y < l\) 或 \(x>r\)),则直接返回。
- 若该节点为叶子节点且就是将要修改的点,修改并返回。
- 除上述两种情况外,继续递归左右子树并合并答案。
延迟标记(\(Lazy-Tag\))
我们很多时候解决的不仅是单点修改,区间查询,而是区间修改,区间查询。
假设区间修改操作是将一个区间同时加上一个数。我们在节点\(k\)上维护一个值\(tag_k\),表示\(k\)这个节点中所有数都加上了\(tag_k\)。此时,答案就是路径上包含这个点的节点的\(tag_i\)之和
但这样是不可行的,应为我们需要修改所有可能影响这个区间的节点,最坏复杂度可能达到\(O(n)\)。
我们将单点查询,区间修改与区间查询,单点修改结合起来,及维护每个节点的和(\(sum_k\)),又维护加和标记(\(tag_k\))。我们称这样的标记叫做延迟标记(\(Lazy-Tag\))
标记下传
执行修改操作时,我们先找到对应节点\(k\),并修改\(tag_k\),并更新\(sum_k\)。但我们无法直接修改子节点的区间和,因为子节点数量很多,时间复杂度高。我们在处理\(k\)区间时完全没有必要去更新它的子节点,我们只需要将\(tag_k\)的值向下传递并清空,在需要用到子节点时在进行计算。
代码实现
namespace SGT {
const int N=1e5+5;
int n, src[N], seg[N<<2], lz[N<<2];
#define lc (p<<1)
#define rc (p<<1|1)
#define mid ((l+r)>>1)
void build(int p=1, int l=1, int r=n) { // 建树
if(l == r) { seg[p] = src[l]; return; }
build(lc,l,mid); build(rc,mid+1,r);
seg[p] = seg[lc] + seg[rc];
}
void push_down(int p, int l, int r) { // 下传标记
if(!lz[p]) return;
seg[lc] += lz[p]*(mid-l+1);
seg[rc] += lz[p]*(r-mid);
lz[lc] += lz[p], lz[rc] += lz[p];
lz[p]=0;
}
void update(int L, int R, int v, int p=1, int l=1, int r=n) { // 区间更新
if(L<=l && r<=R) { seg[p] += v*(r-l+1); lz[p] += v; return; }
push_down(p,l,r);
if(L<=mid) update(L,R,v,lc,l,mid);
if(R>mid) update(L,R,v,rc,mid+1,r);
seg[p] = seg[lc] + seg[rc];
}
int query(int L, int R, int p=1, int l=1, int r=n) { // 区间查询
if(L<=l && r<=R) return seg[p];
push_down(p,l,r); int s=0;
if(L<=mid) s += query(L,R,lc,l,mid);
if(R>mid) s += query(L,R,rc,mid+1,r);
return s;
}
void init(int _n, int a[]) { // 初始化
n=_n;
for(int i=1;i<=n;++i) src[i]=a[i];
fill(lz,lz+(n<<2)+1,0);
build();
}
};

浙公网安备 33010602011771号