AcWing 算法提高课 懒标记线段树

pushup是由子节点算父节点

对于懒标记线段树,需要有pushdown函数,将修改向下传播。

 

对于一个懒标记add,其意义为,给以当前节点为根的子树中(不包含自己)的每一个节点加上add。

 

查询时,需要将祖先节点上的add值累加到当前节点。

实现方法是,在查询过程中,如果需要递归,则将add标记清空,并传播到子节点,即,在查询时加入pushdown。

修改过程中同样需要向下传播,只要是需要向下递归,就要传播。

举例:

 

 

 

这样就可以支持log的区间修改了

模板:维护区间和的懒标记线段树

复制代码
const int N=100010;

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

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

void pushdown(int u)
{
    auto &root=tr[u],&left=tr[2*u],&right=tr[2*u+1];
    if(tr[u].add)
    {
        left.add+=root.add;right.add+=root.add;
        left.sum+=root.add*(left.r-left.l+1);
        right.sum+=root.add*(right.r-right.l+1);
        root.add=0;
    }
}
void build(int u,int l,int r)
{
    if(l==r)
    {
        tr[u]={l,r,0,0};
    }
    else
    {
        tr[u]={l,r,0,0};
        int mid=(l+r)/2;
        build(2*u,l,mid);
        build(2*u+1,mid+1,r);
        pushup(u);
    }
}

void modify(int u,int l,int r,LL val)
{
    if(l<=tr[u].l&&r>=tr[u].r)//当前节点被完全包含,不再继续向下
    {
        tr[u].sum+=val*(tr[u].r-tr[u].l+1);
        //修改懒标记
        tr[u].add+=val;
        
    }
    else//当前节点的区间过大,需要分裂
    {
        //分裂必须先pushdown
        pushdown(u);
        int mid=(tr[u].l+tr[u].r)/2;
        if(mid>=l) modify(2*u,l,r,val);
        if(mid+1<=r) modify(2*u+1,l,r,val);
        //子区间修改后,需要pushup
        pushup(u);
    }
}

LL query(int u,int l,int r)
{
    if(l<=tr[u].l&&r>=tr[u].r)//完全包含,直接返回
    {
        return tr[u].sum;
    }
    else//查询子区间
    {
        //分裂操作必须先pushdown
        pushdown(u);
        int mid=(tr[u].l+tr[u].r)/2;
        LL res=0;
        if(mid>=l) res+=query(2*u,l,r);
        if(mid+1<=r) res+=query(2*u+1,l,r);
        return res;
    }
}
void YD()
{   
    int n,m;cin>>n>>m;
    build(1,1,n);
    fore(i,1,n)
    {
        int num;cin>>num;
        modify(1,i,i,num);
    }
    while(m--)
    {
        char op;cin>>op;
        if(op=='Q')//查询
        {
            int l,r;cin>>l>>r;
            cout<<query(1,l,r)<<endl;
        }
        if(op=='C')//修改
        {
            int l,r,val;cin>>l>>r>>val;
            modify(1,l,r,val);
        }
    }

}
View Code
复制代码

其中,sum维护区间和,add是懒标记,表示其子节点全部加上add

例题:可以进行区间加法和乘法修改,以及查询区间和的懒标记线段树

https://www.acwing.com/problem/content/1279/

重点思路在于维护mul和add两个懒标记,且先乘再加,便于维护。

优化:乘法和加法可以统一乘一个操作,加法就视为先*1,乘法就视为后+0

注意:线段树的节点只向下看,sum维护区间的求和,懒标记维护的是对区间上的每一个点应该的操作。

在pushdown中,先对子节点的区间sum进行相应的修改,然后根据对每个点的影响,再修改子节点的懒标记。

代码:

复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=100010;
int n;
LL mod;
struct Node
{
    int l,r;
    LL sum,mul,add;
}tr[N*4];

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

void mul_add(int u,LL m,LL a)
{
    tr[u].sum=(tr[u].sum*m+a*(tr[u].r-tr[u].l+1))%mod;
    tr[u].mul=tr[u].mul*m%mod;
    tr[u].add=(tr[u].add*m+a)%mod;
    
}
void pushdown(int u)
{
    mul_add(2*u,tr[u].mul,tr[u].add);
    mul_add(2*u+1,tr[u].mul,tr[u].add);
    tr[u].mul=1;tr[u].add=0;
}
void build(int u,int l,int r)
{
    if(l==r)
    {
        tr[u]={l,r,0,1,0};
    }
    else
    {
        tr[u]={l,r,0,1,0};
        int mid=(l+r)/2;
        build(u*2,l,mid);
        build(u*2+1,mid+1,r);
        pushup(u);
    }
}
void modify(int u,int l,int r,LL m,LL a)
{
    if(l<=tr[u].l&&r>=tr[u].r)
    {
        mul_add(u,m,a);
    }
    else
    {
        pushdown(u);
        int mid=(tr[u].l+tr[u].r)/2;
        if(l<=mid) modify(2*u,l,r,m,a);
        if(r>=mid+1) modify(2*u+1,l,r,m,a);
        pushup(u);
    }
}
LL query(int u,int l,int r)
{
    if(l<=tr[u].l&&r>=tr[u].r)
        return tr[u].sum;
    else
    {
        LL res=0;
        pushdown(u);
        int mid=(tr[u].l+tr[u].r)/2;
        if(l<=mid) res+=query(2*u,l,r);
        if(r>=mid+1) res+=query(2*u+1,l,r);
        return res%mod;
    }
}
int main()
{

    cin>>n>>mod;
    build(1,1,n);
    for(int i=1;i<=n;i++)
    {
        int num;
        cin>>num;
        modify(1,i,i,1,num);
    }
    int p;cin>>p;
    while(p--)
    {
        int t;cin>>t;
        if(t==1)
        {
            int a,b,c;cin>>a>>b>>c;
            modify(1,a,b,c,0);
        }
        else if(t==2)
        {
            int a,b,c;cin>>a>>b>>c;
            modify(1,a,b,1,c);
        }
        else
        {
            int a,b;cin>>a>>b;
            cout<<query(1,a,b)<<endl;
        }
    }
}
View Code
复制代码

 

posted @   80k  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示