动态修改

作为一颗线段树,如果它只能查询而不能修改,就很容易被上位替代,而支持在 下进行区间修改和区间查询,则是线段树最大的优势

要实现区间修改,我们需要引入 lazy propagation懒标记

懒标记的作用我们用如下例子表示:

假设我们需要对区间 [2,5] 的元素都+10,且当前节点[1,8] 覆盖了 [2,5]

我们可以:

  • 不立即递归更新子节点
  • 在当前节点打上懒标记
  • 记录这个区间整体应该要+10
  • 只有当 下次访问到这个区间的子节点的时,才下传这个标记

变化

支持区间查询的线段树的线段树相比,我们的结构体的存储信息应该多加上懒标记的信息

同时我们需要多加一些函数来应用和下传懒标记,以及区间修改

我们这次以支持模下运算的加法和乘法演示

struct Seg
{
    struct Node
    {
        i64 sum = 0, add = 0, mul = 1;
    };
    i64 n;
    i64 mod;
    vector<Node> seg;
    Seg(i64 n, i64 mod)
    {
        this->n = n;
        this->mod = mod;
        seg.resize((n << 2) + 4);
    }
    // 合并逻辑
    Node merge(const Node &L, const Node &R)
    {
        Node res;
        res.sum = (L.sum + R.sum) % mod;
        res.add = 0;
        res.mul = 1;
        return res;
    }
    //懒标记应用
    void applyadd(i64 idx, i64 l, i64 r, i64 val)
    {
        seg[idx].add = (seg[idx].add + val) % mod;
        seg[idx].sum = (seg[idx].sum + val * (r - l + 1) % mod) % mod;
    }
    void applymul(i64 idx, i64 l, i64 r, i64 val)
    {
        seg[idx].mul = (seg[idx].mul * val) % mod;
        seg[idx].sum = seg[idx].sum * val % mod;
        seg[idx].add = seg[idx].add * val % mod;
    }
    // 懒标记向下应用
    void push(i64 idx, i64 l, i64 r)
    {
        i64 &_add = seg[idx].add;
        i64 &_mul = seg[idx].mul;
        if (_add == 0 && _mul == 1) return;
        i64 mid = (l + r) >> 1;
        applymul(idx << 1, l, mid, _mul);
        applymul(idx << 1 | 1, mid + 1, r, _mul);
        applyadd(idx << 1, l, mid, _add);
        applyadd(idx << 1 | 1, mid + 1, r, _add);
        _add = 0;
        _mul = 1;
    }
    void build(i64 idx, i64 l, i64 r, const vint &a)
    {
        if (l == r) {
            seg[idx].sum = a[l];
            seg[idx].add = 0;
            return;
        }
        i64 mid = (l + r) >> 1;
        build(idx << 1, l, mid, a);
        build(idx << 1 | 1, mid + 1, r, a);
        seg[idx] = merge(seg[idx << 1], seg[idx << 1 | 1]);
    }
    // 1是加法,2是乘法
    void update(i64 idx, i64 l, i64 r, i64 ql, i64 qr, i64 val, i64 op)
    {
        if (op == 1) {
            if (ql <= l && r <= qr) {
                applyadd(idx, l, r, val);
                return;
            }
        }
        if (op == 2) {
            if (ql <= l && r <= qr) {
                applymul(idx, l, r, val);
                return;
            }
        }
        push(idx, l, r);
        i64 mid = (l + r) >> 1;
        if (ql <= mid) update(idx << 1, l, mid, ql, qr, val, op);
        if (qr > mid) update(idx << 1 | 1, mid + 1, r, ql, qr, val, op);
        seg[idx] = merge(seg[idx << 1], seg[idx << 1 | 1]);
    }
    Node query(i64 idx, i64 l, i64 r, i64 ql, i64 qr)
    {
        if (ql <= l && r <= qr) return seg[idx];
        push(idx, l, r);
        i64 mid = (l + r) >> 1;
        i64 res = 0;
        if (qr <= mid)
            return query(idx << 1, l, mid, ql, qr);
        else if (ql > mid)
            return query(idx << 1 | 1, mid + 1, r, ql, qr);
        else
            return merge(query(idx << 1, l, mid, ql, qr), query(idx << 1 | 1, mid + 1, r, ql, qr));
    }
    void build(const vint &a)
    {
        build(1, 1, n, a);
    }
    void update(i64 l, i64 r, i64 val, i64 op)
    {
        update(1, 1, n, l, r, val, op);
    }
    i64 query(i64 l, i64 r)
    {
        return query(1, 1, n, l, r).sum % mod;
    }
};

其实主要思想就是懒标记的使用和下传