[18/03/24] 線段樹模板

四氧化二磷發表於2024-03-18
//P3373
#include<bits/stdc++.h>
#define N 1000000          //N取決於n的上限
#define ll long long
using namespace std;
ll n;                      //n為區間大小
ll m;                      //m為查詢次數
ll a[N];                   //初始區間值輸入
ll tr[N * 4];              //線段樹陣列
ll mulaz[N * 4];           //乘法懶惰標記
ll adlaz[N * 4];           //加法懶惰標記  
ll k;                      //區間操作的值
ll L,R;                    //操作和查詢區間
ll M;                      //M為模數(若存在)
ll bol;                    //操作型別
ll Ans;                    //查詢結果
ll read(){                 //快讀函式
   ll s = 0,w = 1;
   char ch=getchar();
   while(ch < '0' || ch > '9'){if(ch == '-')w *= -1;ch = getchar();}
   while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0',ch = getchar();
   return s * w;
}
void update(ll l,ll r,ll p){                //注意更新函式不遞迴
    ll s = (p << 1),t = (p << 1) | 1,mid = (l + r) >> 1;
    if(mulaz[p] - 1){                       //若乘法懶惰標記存在
        tr[s] *= mulaz[p];                  //下放左子樹
        tr[s] %= M;
        mulaz[s] *= mulaz[p];
        mulaz[s] %= M;
        adlaz[s] *= mulaz[p];
        adlaz[s] %= M;
        tr[t] *= mulaz[p];                  //下放右子樹
        tr[t] %= M;
        mulaz[t] *= mulaz[p];
        mulaz[t] %= M;
        adlaz[t] *= mulaz[p];
        adlaz[t] %= M;
    }
    if(adlaz[p]){                           //若加法懶惰標記存在,加法不影響倍率
        tr[s] += adlaz[p] * (mid - l + 1);  //下放左子樹
        tr[s] %= M;
        adlaz[s] += adlaz[p];
        adlaz[s] %= M;
        tr[t] += adlaz[p] * (r - mid);      //下放右子樹
        tr[t] %= M;
        adlaz[t] += adlaz[p];
        adlaz[t] %= M;
    }
    mulaz[p] = 1;                           //重置標記
    adlaz[p] = 0;
}
void add(ll x){                             //建樹過程,更新父節點
    tr[x] = (tr[x << 1] + tr[(x << 1) | 1]) % M;
}
void build(ll l,ll r,ll p){                 //建樹過程
    mulaz[p] = 1;                           //初始化標記
    adlaz[p] = 0;
    if(l == r){                             //終止條件(所以線段樹是二叉平衡搜尋樹)
        tr[p] = a[l];
        return;
    }
    ll mid = l + ((r - l) >> 1);
    build(l,mid,(p << 1));                  //建立左子樹
    build(mid + 1,r,(p << 1) | 1);          //建立右子樹
    add(p);                                 //得到該節點原始資訊
}
void multi(ll l,ll r,ll p){                 //乘法操作
    if(L <= l && r <= R){                   //若該區間完全包含於操作區間
        mulaz[p] *= k;                      //乘法懶惰標記
        mulaz[p] %= M;
        adlaz[p] *= k;                      //加法懶惰標記,相當於展開
        adlaz[p] %= M;
        tr[p] *= k;                         //更新該節點資訊
        tr[p] %= M;
        return;                             //當前可以不再往下遞迴而儲存標記
    }
    update(l,r,p);                          //若該節點上存在懶惰標記,則將懶惰標記下放子節點,確保不會出現父節點有懶惰標記且子節點有懶惰標記的混亂狀態
    ll mid = l + ((r - l) >> 1);
    if(L <= mid){                           //如果左子樹部分包含於
        multi(l,mid,(p << 1));
    }
    if(mid + 1 <= R){                       //如果右子樹部分包含於
        multi(mid + 1,r,(p << 1) | 1);
    }
    add(p);                                 //更新其父節點
}
void add1(ll l,ll r,ll p){                  //加法操作
    if(L <= l && r <= R){                   //若該區間完全包含於操作區間
        adlaz[p] += k;                      //加法懶惰標記,加法不影響倍率
        adlaz[p] %= M;
        tr[p] += k * (r - l + 1);           //更新節點資訊
        tr[p] %= M;
        return;
    }
    update(l,r,p);                          //下放懶惰標記(更新子節點資訊)
    ll mid = l + ((r - l) >> 1);
    if(L <= mid){                           //如果左子樹部分包含於
        add1(l,mid,(p << 1));
    }
    if(mid + 1 <= R){                       //如果右子樹部分包含於
        add1(mid + 1,r,(p << 1) | 1);
    }
    add(p);                                 //更新其父節點
}
ll getans(ll l,ll r,ll p){
    if(L <= l && r <= R){
        return tr[p];
    }
    update(l,r,p);
    ll mid = l + ((r - l) >> 1);
    ll tot = 0;
    if(L <= mid){
        tot += getans(l,mid,(p << 1));
    }
    tot %= M;
    if(mid + 1 <= R){
        tot += getans(mid + 1,r,(p << 1) | 1);
    }
    return tot % M;
}
int main(){
    n = read();
    m = read();
    M = read();
    for(int i = 1;i <= n;i++){
        a[i] = read();
    }
    build(1,n,1);
    for(int i = 1;i <= m;i++){
        bol = read();
        if(bol == 1){
            L = read();
            R = read();
            k = read();
            multi(1,n,1);
        }
        if(bol == 2){
            L = read();
            R = read();
            k = read();
            add1(1,n,1);
        }
        if(bol == 3){
            L = read();
            R = read();
            Ans = getans(1,n,1);
            printf("%lld\n",Ans);
        }
    }
    return 0;
}

//懶惰標記:若某節點的懶惰標記不為預設值,則說明:
//1.該節點資訊已更新
//2.該節點的所有父節點(根節點到該節點的路徑上的所有節點)的懶惰標記均為預設值
//3.該節點的懶惰標記未下放子節點
//注意:標記僅給予對應區間完全包含於操作區間內的節點

//關於子節點和父節點的關係:
//1.若父節點對應區間整體包含於操作區間內,不下放子區間
//2.否則:
//  1.父節點的懶惰標記下放
//  2.子節點在更新資訊後更新父節點(因為父節點僅部分包含於操作區間內,並未給父節點新增標記,
//    則父節點的資訊仍未更新,更新操作對應操作函式末尾 "add(p)" )

//Ex: a[] = [1,1,1,1,1]
//現將 [2,4] 區間內均 +1 ,則 tr[3] 對應區間 [4,5] 中僅有 [4] 更新,此時需要更新 tr[3]



//最佳化思路:
//1.發現建樹時確定的 p -> l_p,r_p 在查詢和修改操作時不變化,即可以使用結構體最佳化函式參量

相關文章