线段树用于动态的区间更新和查询(区间最大值、最小值、加和、最大公约数、最小公倍数等)

各节点除了保存下标区间,还应保存该区间对应的聚合值。叶节点对应单点区间,内节点对应虚拟的合并区间。若某内节点表示区间[i..j],那左子节点表示区间[i..(i+j)/2],右子节点表示区间[(i+j)/2+1..j]。

数组实现

用n个叶节点保存n个数组元素,还需要n-1个内节点。类似堆的数组实现,数组大小2n,不用tree[0],叶节点从n开始。父子关系:p=i/2、lchild=2*i、rchild=2*i+1。每个叶节点(i>=n)表示区间nums[i..i],每个内节点(i<n)表示左子节点和右子节点的合并区间。各节点表示的区间是固定的,节点中不再显式记录,只记录聚合值。三个操作:构建、更新、查询。

class SegmentTree {
    int _n;
    vector<int> _tree;
public:
    SegmentTree(const vector<int> &nums) 
        : _n(nums.size()), _tree(2 * _n) {
        build(nums); 
    }
    
    void build(const vector<int> &nums) {
        // 叶节点是原数组元素
        for (int i = _n; i < 2 * _n; i++) {
            _tree[i] = nums[i-_n];
        }
        // 内节点计算聚合信息
        for (int i = _n - 1; i >= 1; i--) {
            _tree[i] = _tree[2*i] + _tree[2*i+1];
        }
    }
    
    void update(int i, int val) {
        i += _n;
        _tree[i] = val;
        for (int j = i/2; j >= 1; j /= 2) {
            _tree[j] = _tree[2*j] + _tree[2*j+1];
        }
    }

    // 返回nums[i..j]的和
    int query(int i, int j) {
        int sum = 0;
        for (i += _n, j += _n; i <= j; i /= 2, j /= 2) {
            if (i % 2 == 1) sum += _tree[i++];
            if (j % 2 == 0) sum += _tree[j--];
        }
        return sum;
    }
};

query()的解释见http://codeforces.com/blog/entry/18051

General idea is the following. If l, the left interval border, is odd (which is equivalent to l&1) then l is the right child of its parent. Then our interval includes node l but doesn't include it's parent. So we add t[l] and move to the right of l's parent by setting l = (l + 1) / 2. If l is even, it is the left child, and the interval includes its parent as well (unless the right border interferes), so we just move to it by setting l = l / 2. Similar argumentation is applied to the right border. We stop once borders meet.

递归实现

递归树实现
递归数组实现

每个treeIdx对应范围[lo,hi],lo==hi是叶节点,build、query、update都是用后序遍历merge两子树解。因为只用递归向下计算子节点,不用计算父节点,可以使用tree[0],子节点变为:lchild=2*i+1、rchild=2*i+2。因为递归到叶节点结束,算空节点凑成完全二叉树,最多4n个节点(叶节点n,最底层空叶节点最多n,全部叶节点最多2n,全部节点最多4n)。

正常线段树一次更新一个元素,并把更新沿着"祖父=>叶"的路径传播。要想一次更新一个范围,用lazy propagation技术,意为:对子节点的更新延迟处理,先记录到与tree[]同样大小的lazy[]数组中。更新时先应用本节点的lazy[i]更新(并把本该传播给子节点的更新记录到lazy[lchild]、lazy[rchild],lazy[i]清零),再应用本次更新调用的更新(并把本该传播给子节点的更新记录到lazy[lchild]、lazy[rchild]);查询时,先应用本节点的lazy[i]更新(并把本该传播给子节点的更新记录到lazy[lchild]、lazy[rchild],lazy[i]清零),然后递归查询。