[18/03/24] 线段树模板

//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 在查询和修改操作时不变化,即可以使用结构体优化函数参量
posted @ 2024-03-18 01:07  四氧化二磷  阅读(1)  评论(0编辑  收藏  举报