线段树 (区间查询)

结构

线段树是一个平衡的二元树，所有叶子到根的距离最多只差1。令整个区间的长度为N，则其有N个叶节点，每个叶节点代表一个单位区间，每个内部结点代表的区间为其两个儿子代表区间的联集。

基本操作

线段树所要提供的是查询一个区间内的信息，并允许修改操作。要使用线段树，此信息必须满足对于区间与位于区间内的一点，要可以由、求得。例如范围最值查询即匹配此条件。
代码中, rt指的是root, 当前子树的根节点; l, r指的是当前子树所统计的区间利用完全二叉堆的性质来保存节点编号, 所以rt << 1是左子树的节点, rt << 1 | 1是右子树的节点在查询和成端更新操作中的L和R是指修改或者查询的区间

节点数据向上更新

将子节点的值更新到父节点。
/* 对于区间求和*/void push_up(int rt) {
    tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];}/* 对于区间求最大值*/void push_up(int rt) {
    tree[rt] = max(tree[rt << 1], tree[rt << 1 | 1]);}

节点懒惰标记下推

对于区间求和, 原子数组值需要加上lazy标记乘以子树所统计的区间长度。
len为父节点统计的区间长度, 则len - (len >> 1)为左子树区间长度, len >> 1为右子树区间长度。
void push_down(int rt, int len) {
    tree[rt << 1] += lazy[rt] * (len - (len >> 1));
    lazy[rt << 1] += lazy[rt];
    tree[rt << 1 | 1] += lazy[rt] * (len >> 1);
    lazy[rt << 1 | 1] += lazy[rt];
    lazy[rt] = 0;}
对于区间求最大值, 子树的值不需要乘以长度, 所以不需要传递参数len。
void push_down(int rt) {
    tree[rt << 1] += lazy[rt];
    lazy[rt << 1] += lazy[rt];
    tree[rt << 1 | 1] += lazy[rt];
    lazy[rt << 1 | 1] += lazy[rt];
    lazy[rt] = 0;}

建树

新建一棵长度N的线段树。
#define lchild rt << 1, l, m#define rchild rt << 1 | 1, m + 1, rvoid build(int rt = 1, int l = 1, int r = N) {
    if (l == r) { std::cin >> tree[rt]; return; }
    int m = (l + r) >> 1;
    build(lchild); build(rchild);
    push_up(rt);}

更新

单点更新, 不需要用到lazy标记
#define lchild rt << 1, l, m#define rchild rt << 1 | 1, m + 1, rvoid update(int p, int delta, int rt = 1, int l = 1, int r = N) {
    if (l == r) {
        tree[rt] += delta;
        return;
    }
    int m = (l + r) >> 1;
    if (p <= m) update(p, delta, lchild);
    else update(p, delta, rchild);
    push_up(rt);}
成段更新, 需要用到lazy标记来提高时间效率
#define lchild rt << 1, l, m#define rchild rt << 1 | 1, m + 1, rvoid update(int L, int R, int delta, int rt = 1, int l = 1, int r = N) {
    if (L <= l && r <= R) {
        tree[rt] += delta * (r - l + 1);
        lazy[rt] += delta;
        return;
    }
    if (lazy[rt]) push_down(rt, r - l + 1);
    int m = (l + r) >> 1;
    if (L <= m) update(L, R, delta, lchild);
    if (R > m)  update(L, R, delta, rchild);
    push_up(rt);}

区间查询

#define lchild rt << 1, l, m#define rchild rt << 1 | 1, m + 1, rint query(int L, int R, int rt = 1, int l = 1, int r = N) {
    if (L <= l && r <= R) return tree[rt];
    if (lazy[rt]) push_down(rt, r - l + 1);
    int m = (l + r) >> 1, ret = 0;
    if (L <= m) ret += query(L, R, lchild);
    if (R > m)  ret += query(L, R, rchild);
    return ret;}

可直接使用的编程模板

#include <iostream>#include <cstdio>#include <cmath>#include <cstring>using namespace std;namespace SegTree {
#define maxn 1000000
class SegmentTree {
#define lson (o<<1)
#define rson (o<<1|1)
#define mid ((l+r)>>1)
public :
int addv[maxn], maxv[maxn], minv[maxn], sumv[maxn];
int arr[maxn];
int N;
private:int _max(const int& _, const int& __) { return _>__?_:__; }
private:int _min(const int& _, const int& __) { return _<__?_:__; }
public : int pushup(int o) {
minv[o] = _min(minv[lson], minv[rson]);
maxv[o] = _max(maxv[lson], maxv[rson]);
sumv[o] = sumv[lson] + sumv[rson];
return 0;
}
public : int pushdown(int o, int l, int r) {
if(addv[o] == 0) return -1;
addv[lson] += addv[o]; addv[rson] += addv[o];
minv[lson] += addv[o]; minv[rson] += addv[o];
maxv[lson] += addv[o]; maxv[rson] += addv[o];
sumv[lson] += addv[o] * (mid-l+1); sumv[rson] += addv[o] * (r-mid);
addv[o] = 0;
}
public : int Build(int o, int l, int r) {
addv[o] = 0;
if(l == r) {
maxv[o] = arr[l];minv[o] = arr[l];sumv[o] = arr[l];
return 0;
}
Build(lson, l, mid);
Build(rson, mid+1, r);
pushup(o);
return 0;
}
public : int optadd(int o, int l, int r, int ql, int qr, int addval) {
if(ql > r or qr < l) return 0;
if(ql <= l and r <= qr) {
addv[o] += addval;
sumv[o] += addval * (r-l+1);
return 0;
}
pushdown(o, l, r);
optadd(lson, l, mid, ql, qr, addval);
optadd(rson, mid+1, r, ql, qr, addval);
pushup(o);
}
public : int query_sum(int o, int l, int r, int ql, int qr) {
if(ql > r or qr < l) return 0;
if(ql <= l and r <= qr) {
return sumv[o];
}
pushdown(o, l, r);
return query_sum(lson, l, mid, ql, qr) + query_sum(rson, mid+1, r, ql, qr);
}
public : int query_min(int o, int l, int r, int ql, int qr) {
if(ql > r or qr < l) return 0;
if(ql <= l and r <= qr) {
return minv[o];
}
pushdown(o, l, r);
return _min(query_min(lson, l, mid, ql, qr), query_min(rson, mid+1, r, ql, qr));
}
public : int query_max(int o, int l, int r, int ql, int qr) {
if(ql > r or qr < l) return 0;
if(ql <= l and r <= qr) {
return maxv[o];
}
pushdown(o, l, r);
return _max(query_max(lson, l, mid, ql, qr), query_max(rson, mid+1, r, ql, qr));
}
};} 
//End of SegmentTreeusing namespace SegTree;

变种

zkw线段树是一种自底向上的线段树，由清华大学的张昆玮提出。它相对于传统线段树的优势体现在减少了递归操作和增加了位运算等操作以减少常数。

相关链接

http://dongxicheng.org/structure/segment-tree/