创建线段树O(n)
原数据的大小为N(例如维护区间合时,原数组总数N),则线段树要开4 * N
int n, w[N];
struct node{
int l, r, sum;
}tr[N * 4];
void build(int p, int l, int r){
tr[p] = {l, r, w[l]}; // leaf node l == r sum = w[l];
if (l == r) return;
int m = l + r >> 1;
build(lc, l , m); // recursive rigth and left
build(rc, m + 1, r);
tr[p].sum = tr[lc].sum + tr[rc].sum; // up make w[l] plus w[r]
}
点修改O(logn)
从根结点进入,递归找到叶子结点[x, x], 把该节点的值增加x。从下往上更新祖先节点。
void update(int p, int x, int k){
if (tr[p].l == x && tr[p].r == x){ // if leaf node return
tr[p].sum += k;
return;
}
int m = tr[p].l + tr[p].r >> 1; // update the road from root to leaf node.
if (x <= m) update(lc, x, k);
if (x > m) update(rc, x, k);
tr[p].sum = tr[lc].sum + tr[rc].sum;
}
区间查询O(logn)
拆分与拼凑的思想。例如,查询区间 [4, 9]
可以拆分成 [4, 5], [6, 8], [9, 9]
通过合并这三个区间的答案求查询答案。
从根节点进入,递归执行一下过程:
1. 若查询区间 `[x, y]` 完全覆盖当前节点区间则立即回溯,并返回该节点的数值
1. 因为线段树保证了每个区间都不会有重叠,所以我们递归遍历左右节点,如果查询区间包含在内则递归访问相应的左右子树
int query(int p, int x, int y){
if (x <= tr[p].l && tr[p].r <= y)
return tr[p].sum;
int m = tr[p].l + tr[p].r >> 1;
int sum = 0;
if (x <= m) sum += query(lc, x, y);
if (y > m) sum += query(rc, x, y);
return sum;
}
区间修改 (lazy tag)O(logn)
如果进行单点修改的话,那么每次都要把[4, 5]的所有叶子节点修改,然后再向上更新祖先节点,时间复杂度时O(n)的。
优化:
我们做*** 懒惰修改, 当[x, y] 完全覆盖节点区间[a, b]时, 先修改区间的sum值,再打上一个“懒标记”,先修改该区间的sum数值,在打赏一个懒标记,然后立即返回。等下次需要的时候,在下传“懒标记”,然后立即返回。等下次需要时,在下传“懒标记”。这样,可以把每次修改和查询时间都控制到O(logn)***