[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 在查询和修改操作时不变化,即可以使用结构体优化函数参量
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验