线段树 + 多个懒标记: 维护序列

本文详细介绍了线段树结构中懒惰 propagation 的实现方法,包括节点属性的维护、如何通过 add 和 mul 属性更新 sum 值,以及如何处理加法和乘法操作。通过实例展示了如何在代码中应用这些概念,解答了如何将线段树节点的修改操作转化为简单的加法和乘法,并提供了完整的 C++ 代码实现。
摘要由CSDN通过智能技术生成

题目链接:https://www.luogu.com.cn/problem/P2023

分析:

线段树的节点:

struct Node

{

int l,r;

int add; // 懒标记 加法

int mul;  // 懒标记 乘法

int sum; // 真正要求的值

}

1.如何通过属性更新sum值。

,如何将sum 的值通过 add 和 mul进行更新,和之前的线段树中的“你能回答这些问题吗”的题目构造节点属性的思想类似,你能回答这些问题嘛的题解链接 , 在这道题中为了能够算出对应的值,添加了许多属性。

如何通过add,mul属性得到我们需要的sum属性呢?可以发现有这么一种维护方式:

( sum + add ) * mul 的方式, (也就是将每次的操作都转为存储add,mul,然后最后用这个方式计算)  这个方式的话 乘法修改很容易修改。 (sum + add) * mul * c.

所以只要将前mul 改为 mul * c即可。  但是加法呢?

(sum + add) * mul + d, 这个时候就会发现这样维护的方式无法通过子节点更新父结点的add懒标记属性值。

那么是否要向 “你能回答这些问题嘛”那道题目一样添加新的属性呢?

实际上是不用的,我们可以用另一种计算sum的方式

sum * mul + add 的方式,

乘法: (( sum * mul ) + add ) * m;

则mul改为 mul * m ,add改为 add * m

而加法: sum * mul + add + m , 所以add 改为add + m即可。

所以可以很容易的进行维护。

同时还有一个技巧,我们这道题目有两个修改操作,加法修改,乘法修改,

是否需要使用不同的函数对其进行操作呢?  

实际上是不需要的,

sum * a 可以 转化为 sum * a + 0

sum + b 可以转化为 sum * 1 + b;

所以乘法修改和加法修改都可以改为 乘 和 加 合并修改。

最后,进行相应的操作

pushup(),修改父结点的sum值, root .sum = leftson.sum + right.sum

pushdown, 

根据公式 (sum * a + b ) * c + d ==  sum * a * c + b * c + d;  

所以一个根节点 和 add值 以及 mul值的修改

sum = sum * mul + (r - l + 1) * d;

mul = mul * c;

add   = add * c + d

其他操作基本不变。

懒标记模板题题解

 代码实现:

# include <iostream>
using namespace std;

const int N = 100010;

int a[N];

struct Node
{
    int l,r;
    int sum;
    int add;
    int mul;
}edgs[N * 4];

int n,m,p;

void pushup(int u)
{
    edgs[u].sum = (edgs[2 * u].sum + edgs[2 * u +1].sum) % p;
}

void eval(int root , int d,int m) //将root节点更新新的add,mul等懒标记,以及新的sum值
{
    edgs[root].sum = ( (long long)m * edgs[root].sum  +(long long)(edgs[root].r - edgs[root].l +1 ) * d ) % p; // 需要注意到其中的区间加的修改整个区间都加add值
    edgs[root].mul = (long long)edgs[root].mul * m % p;
    edgs[root].add =  ( (long long)edgs[root].add * m + d ) % p;
}

void pushdown(int u)
{
    //将懒标记对应的值下放到子节点
    eval(2 * u , edgs[u].add , edgs[u].mul);
    eval(2 * u + 1 , edgs[u].add , edgs[u].mul);

    // 清空根节点的懒标记操作
    edgs[u].add = 0;
    edgs[u].mul = 1;
}

void build(int u , int l , int r)
{
    edgs[u].l = l, edgs[u].r = r, edgs[u].add = 0 , edgs[u].mul = 1;
    if(l == r)
    {
        edgs[u].sum  = a[l];
        return;
    }
    else
    {
        int mid = ( edgs[u].l + edgs[u].r ) / 2;
        build(2 * u , l , mid);
        build(2 * u +1 , mid + 1 , r);
        //而非叶子节点的edgs[u].sum的属性值是由pushup产生的。所以它们是否赋值都可以
        pushup(u);
    }
}

void modify(int u , int l , int r , int add , int mul)
{
    if(edgs[u].l >=  l && edgs[u].r <= r)
    {
        eval(u,add,mul);
        return;
    }
    else
    {
        pushdown(u);
        int mid = ( edgs[u].l + edgs[u].r ) / 2;
        if(l <= mid)
        {
            modify(2 * u ,l , r , add , mul);
        }
        if(r > mid)
        {
            modify(2 * u + 1 , l , r,  add , mul);
        }
        pushup(u);
    }
}

int query(int u , int l ,int r)
{
    if(edgs[u].l >= l && edgs[u].r <= r)
    {
        return edgs[u].sum;
    }
    else
    {
        pushdown(u);
        int mid = ( edgs[u].l + edgs[u].r ) / 2;

        int sum = 0;

        if(l <= mid)
        {
            sum = query(2 * u , l , r) % p;
        }
        if(r > mid)
        {
            sum =( (long long)sum + query(2 * u +1 , l , r) ) % p;
        }
        return sum;
    }
}

int main()
{
    scanf("%d %d",&n,&p);
    for(int i = 1 ; i <= n ; i++)
    {
        scanf("%d",&a[i]);
    }
    build(1,1,n);
    scanf("%d",&m);
    while(m--)
    {
        int a;
        scanf("%d",&a);;
        if(a == 1)
        {
            int t ,g,c;
            scanf("%d %d %d",&t,&g,&c);
            modify(1,t,g,0,c); // 乘法操作,所以add == 0 , mul = c
        }
        else if(a == 2)
        {
            int t ,g,c;
            scanf("%d %d %d",&t,&g,&c);
            modify(1,t,g,c,1); // 加法操作,所以add == c , mul = 1
        }
        else
        {
            int t,g;
            scanf("%d %d",&t,&g);
            printf("%d\n",query(1,t,g));
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值