线段树(3)——区间操作叠加

如果我既有区间乘法又有区间加法,我应该怎么办呢?

这时候需要写两个标记。假设只写一个标记。

标记加法:此时对于乘法操作,因为是将 ti+lazyi 乘以 x,这样子显然一个懒惰标记做不到。

标记乘法:那我加法咋办?

那两个标记怎么用呢?首先假设加法标记为 lazy,乘法标记为 multi。如果将这两个标记分开显然是不可能的,因为每个点的标记是互相依赖的。而如果一直 push_down 需要花费 logn 的时间,这与线段树 logn 的标准就不一样了,别忘了还有线段树本身的操作。如果仅仅是给 now 下放,那么子结点会遇到一样的问题。一般我们做题用的线段树数量是根据查询的种类来的。假设这里只有区间加法查询,那么应该怎么做呢?

首先考虑 t 的变化,显然在加法操作时,应当 tnow+=x×(trtl+1),在乘法操作时,应当 tnow×=x。其次,考虑 push_down,对于加法的 push_down,和普通的加法下放一样,因为加法下方是不考虑乘法标记的,而乘法标记需要考虑加法标记。如果反过来,加法下放考虑乘法标记,这时候 lazynow 就可能出现分数,就不太方便了。那么考虑乘法下放。对于一个区间,如果统统乘以 x,那么这个区间的和就乘以 x,这是乘法分配律。所以,乘法标记下放的时候应该让 tnow2(+1)lazynow2(+1) 都乘以 multinow。那么如果将一个区间都乘以 x 再都乘以 y,就相当于乘以 xy,所以 mutlinow2(+1) 应当乘以 multinow。然后将 multinow 清为 1。在写修改和查询的时候,要先下放乘法标记,再下放加法标记,因为乘法标记包含对加法的考虑,所以先下放加法会出现各种各样的问题。

以下代码参考题目

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,p,m,a[N],t[N*8],lazy[N*8],multi[N*8];
void push_down_add(int now,int tl,int tr){
    int mid=(tl+tr)/2;
    t[now*2]=(t[now*2]+lazy[now]*(mid-tl+1))%p;
    t[now*2+1]=(t[now*2+1]+lazy[now]*(tr-mid))%p;
    lazy[now*2]=(lazy[now*2]+lazy[now])%p;
    lazy[now*2+1]=(lazy[now*2+1]+lazy[now])%p;
    lazy[now]=0;
}
void push_down_multi(int now,int tl,int tr){
    int mid=(tl+tr)/2;
    t[now*2]=t[now*2]*multi[now]%p;
    t[now*2+1]=t[now*2+1]*multi[now]%p;
    multi[now*2]=multi[now*2]*multi[now]%p;
    multi[now*2+1]=multi[now*2+1]*multi[now]%p;
    lazy[now*2]=lazy[now*2]*multi[now]%p;
    lazy[now*2+1]=lazy[now*2+1]*multi[now]%p;
    multi[now]=1;
}
void build(int now,int tl,int tr){
    multi[now]=1;
    if(tl==tr){
        t[now]=a[tl];
        return ;
    }
    int mid=(tl+tr)/2;
    build(now*2,tl,mid);
    build(now*2+1,mid+1,tr);
    t[now]=(t[now*2]+t[now*2+1])%p;
}
void modify_add(int now,int tl,int tr,int l,int r,int x){
    if(tl>=l&&tr<=r){
        t[now]=(t[now]+x*(tr-tl+1))%p;
        lazy[now]=(lazy[now]+x)%p;
        return ;
    }
    if(tl>r||tr<l){
        return ;
    }
    if(multi[now]!=1){
        push_down_multi(now,tl,tr);
    }
    if(lazy[now]){
        push_down_add(now,tl,tr);
    }
    int mid=(tl+tr)/2;
    modify_add(now*2,tl,mid,l,r,x);
    modify_add(now*2+1,mid+1,tr,l,r,x);
    t[now]=(t[now*2]+t[now*2+1])%p;
}
void modify_multi(int now,int tl,int tr,int l,int r,int x){
    if(tl>=l&&tr<=r){
        t[now]=t[now]*x%p;
        multi[now]=multi[now]*x%p;
        lazy[now]=lazy[now]*x%p;
        return ;
    }
    if(tl>r||tr<l){
        return ;
    }
    if(multi[now]!=1){
        push_down_multi(now,tl,tr);
    }
    if(lazy[now]){
        push_down_add(now,tl,tr);
    }
    int mid=(tl+tr)/2;
    modify_multi(now*2,tl,mid,l,r,x);
    modify_multi(now*2+1,mid+1,tr,l,r,x);
    t[now]=(t[now*2]+t[now*2+1])%p;
}
int query(int now,int tl,int tr,int l,int r){
    if(tl>=l&&tr<=r){
        return t[now];
    }
    if(tl>r||tr<l){
        return 0;
    }
    if(multi[now]!=1){
        push_down_multi(now,tl,tr);
    }
    if(lazy[now]){
        push_down_add(now,tl,tr);
    }
    int mid=(tl+tr)/2;
    return (query(now*2,tl,mid,l,r)+query(now*2+1,mid+1,tr,l,r))%p;
}
signed main(){
    //freopen("xx.in","r",stdin);
    //freopen("xx.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>p;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    build(1,1,n);
    while(m--){
        int opt,t,g,c;
        cin>>opt;
        if(opt==1){
            cin>>t>>g>>c;
            modify_multi(1,1,n,t,g,c);
        }else if(opt==2){
            cin>>t>>g>>c;
            modify_add(1,1,n,t,g,c);
        }else{
            cin>>t>>g;
            cout<<query(1,1,n,t,g)<<"\n";
        }
    }
    return 0;
}
posted @   吴一鸣  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示