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

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

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

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

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

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

首先考虑 \(t\) 的变化,显然在加法操作时,应当 \(t_{now}+=x\times (tr-tl+1)\),在乘法操作时,应当 \(t_{now}\times =x\)。其次,考虑 \(push\_down\),对于加法的 \(push\_down\),和普通的加法下放一样,因为加法下方是不考虑乘法标记的,而乘法标记需要考虑加法标记。如果反过来,加法下放考虑乘法标记,这时候 \(lazy_{now}\) 就可能出现分数,就不太方便了。那么考虑乘法下放。对于一个区间,如果统统乘以 \(x\),那么这个区间的和就乘以 \(x\),这是乘法分配律。所以,乘法标记下放的时候应该让 \(t_{now*2(+1)}\)\(lazy_{now*2(+1)}\) 都乘以 \(multi_{now}\)。那么如果将一个区间都乘以 \(x\) 再都乘以 \(y\),就相当于乘以 \(xy\),所以 \(mutli_{now*2(+1)}\) 应当乘以 \(multi_{now}\)。然后将 \(multi_{now}\) 清为 \(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 @ 2024-08-25 14:24  吴一鸣  阅读(38)  评论(0编辑  收藏  举报