加载中...

线段树多懒标记

最近在思考:如何对一个序列维护带有两种区间修改的多区间查询问题。这样不可避免地需要对两种修改操作分别维护一种懒标记。但显然,不能将两种懒标记独立看待,因为对于两种操作,先后顺序不同会造成不同的影响。因此如何处理两个懒标记之间的相互影响至关重要, \(pushdown\) 函数需要精心设计。

这里记录一下两种常见的多懒标记设计:

区间加与区间乘 (查询模m意义下的区间和)

乘法比加法的优先级更高,因此先处理乘法,再处理加法。

int m; // 模数为m
// sum
template<typename T>
struct SegTree
{
    struct Node
    {
        int l, r;
        T sum, tag_add, tag_mul;
    }tr[maxn << 2];

    #define lc p<<1
    #define rc p<<1|1

    void pushup(int p)
    {
        tr[p].sum = (tr[lc].sum + tr[rc].sum) % m;
    }

    void pushdown(int p)
    {
        if(tr[p].tag_mul != 1){
			tr[lc].sum = (1ll * tr[lc].sum * tr[p].tag_mul) % m;
			tr[rc].sum = (1ll * tr[rc].sum * tr[p].tag_mul) % m;
			tr[lc].tag_mul = (1ll * tr[lc].tag_mul * tr[p].tag_mul) % m;
			tr[rc].tag_mul = (1ll * tr[rc].tag_mul * tr[p].tag_mul) % m;
			tr[lc].tag_add = (1ll * tr[lc].tag_add * tr[p].tag_mul) % m;
			tr[rc].tag_add = (1ll * tr[rc].tag_add * tr[p].tag_mul) % m;
		}
		tr[p].tag_mul = 1;
		if(tr[p].tag_add != 0){
			tr[lc].sum = (tr[lc].sum + 1ll * (tr[lc].r - tr[lc].l + 1) * tr[p].tag_add % m) % m;
			tr[rc].sum = (tr[rc].sum + 1ll * (tr[rc].r - tr[rc].l + 1) * tr[p].tag_add % m) % m;
			tr[lc].tag_add = (tr[lc].tag_add + tr[p].tag_add) % m;
			tr[rc].tag_add = (tr[rc].tag_add + tr[p].tag_add) % m;
		}
		tr[p].tag_add = 0;
    }

    void build(int p, int l, int r,auto& arr)
    {
        tr[p] = { l,r,arr[l], 0, 1};
        if (l == r) return;
        int mid = l + r >> 1;
        build(lc, l, mid , arr);
        build(rc, mid + 1, r , arr);
        pushup(p);
    }

    void update_add(int p, int l, int r, T k)
    {
        if (l <= tr[p].l && tr[p].r <= r)
        {
            tr[p].sum = (tr[p].sum + 1ll * (tr[p].r - tr[p].l + 1) * k % m) % m;
			tr[p].tag_add = (tr[p].tag_add + k) % m;
            return;
        }
        int mid = tr[p].l + tr[p].r >> 1;
        pushdown(p);
        if (l <= mid) update_add(lc, l ,r, k);
        if (r > mid)  update_add(rc, l ,r, k);
        pushup(p);
    }

	void update_mul(int p, int l, int r, T k)
    {
        if (l <= tr[p].l && tr[p].r <= r)
        {
            tr[p].sum = (1ll * tr[p].sum * k) % m;
			tr[p].tag_add = (1ll * tr[p].tag_add * k) % m; // 之前加过的也要乘上k
			tr[p].tag_mul = (1ll * tr[p].tag_mul * k) % m;
            return;
        }
        int mid = tr[p].l + tr[p].r >> 1;
        pushdown(p);
        if (l <= mid) update_mul(lc, l ,r, k);
        if (r > mid)  update_mul(rc, l ,r, k);
        pushup(p);
    }

    T querysum(int p, int l, int r)
    {
        if (l <= tr[p].l && tr[p].r <= r)
            return tr[p].sum;
        int mid = tr[p].l + tr[p].r >> 1;
        pushdown(p);
        T sum = 0;
        if (l <= mid) sum = (sum + querysum(lc, l, r)) % m;
        if (r > mid) sum = (sum + querysum(rc, l, r)) % m;
        return sum;
    }
};

区间加与区间赋值 (查询区间最大值)

显然区间赋值比区间加的优先级更高,因此在 \(pushdown\) 懒标记时先处理区间赋值的懒标记,再处理区间加的懒标记。

// max
template<typename T>
struct SegTree
{
    struct Node
    {
        int l, r;
        T maxv, tag_fix, tag_add;
    }tr[maxn << 2];

    #define lc p<<1
    #define rc p<<1|1

    void pushup(int p)
    {
		tr[p].maxv = max(tr[lc].maxv, tr[rc].maxv);
    }

    void pushdown(int p)
    {
        // 1
        if(tr[p].tag_fix != -1){ // 先处理区间赋值
            tr[lc].maxv = tr[rc].maxv = tr[p].tag_fix; 
        }
        if(tr[p].tag_add){ // 再处理区间加
            tr[lc].maxv += tr[p].tag_add;
            tr[rc].maxv += tr[p].tag_add;
        }

        // 2
        if(tr[p].tag_fix != -1){
            tr[lc].tag_fix = tr[rc].tag_fix = tr[p].tag_fix;
            tr[lc].tag_add = tr[rc].tag_add = 0;
        }
        if(tr[p].tag_add){
            tr[lc].tag_add += tr[p].tag_add;
            tr[rc].tag_add += tr[p].tag_add;
        }

        // 3,最后不要忘了清空当前结点的懒标记!!!
        tr[p].tag_fix = -1;
        tr[p].tag_add = 0;
    }

    void build(int p, int l, int r,auto& arr)
    {
        tr[p] = { l,r,arr[l],-1,0};
        if (l == r) return;
        int mid = l + r >> 1;
        build(lc, l, mid , arr);
        build(rc, mid + 1, r , arr);
        pushup(p);
    }

    void update_fix(int p, int l, int r, T k)
    {
        if (l <= tr[p].l && tr[p].r <= r) 
        {
			tr[p].maxv = k;
            tr[p].tag_fix = k;
            tr[p].tag_add = 0; // 最后一步是赋值操作,需要把前面的区间价懒标记清空
            return;
        }
        int mid = tr[p].l + tr[p].r >> 1;
        pushdown(p);
        if (l <= mid) update_fix(lc, l ,r, k);
        if (r > mid)  update_fix(rc, l ,r, k);
        pushup(p);
    }

    void update_add(int p, int l, int r, T k)
    {
        if (l <= tr[p].l && tr[p].r <= r)
        {
			tr[p].maxv += k;
            tr[p].tag_add += k; // 先赋值后加,区间加懒标记正常处理即可。
            return;
        }
        int mid = tr[p].l + tr[p].r >> 1;
        pushdown(p);
        if (l <= mid) update_add(lc, l ,r, k);
        if (r > mid)  update_add(rc, l ,r, k);
        pushup(p);
    }

    T querymax(int p, int l, int r)
    {
        if (l <= tr[p].l && tr[p].r <= r) return tr[p].maxv;
        pushdown(p);
        int mid = tr[p].l + tr[p].r >> 1;
        T maxv = INT_MIN;
        if (l <= mid) maxv = max(maxv, querymax(lc, l, r));
        if (r > mid) maxv = max(maxv, querymax(rc, l, r));
        return maxv;
    }

};

线段树多懒标记设计相关例题:

比较考察实际应用的一道题,涉及区间赋值,区间加和区间翻转三种懒标记的同时维护,还有些小巧思在里面:

ABC441 G
code

维护区间翻转和区间赋值两种懒标的典题:

edu23 F
code

posted @ 2026-01-18 21:05  小橘奏  阅读(25)  评论(0)    收藏  举报