線段樹(3)——區間操作疊加

吴一鸣發表於2024-08-25

如果我既有區間乘法又有區間加法,我應該怎麼辦呢?

這時候需要寫兩個標記。假設只寫一個標記。

標記加法:此時對於乘法操作,因為是將 \(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;
}

相關文章