|
|
#include <bits/stdc++.h>
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
const int N = 1000010;
|
|
|
|
|
|
int n, m, p;
|
|
|
|
|
|
// 动态开点线段树
|
|
|
#define LL long long
|
|
|
#define ls tr[u].l // u节点的左子节点号
|
|
|
#define rs tr[u].r // u节点的右儿子节点号
|
|
|
#define mid ((l + r) >> 1)
|
|
|
struct Node {
|
|
|
int l, r;
|
|
|
// 注意:这里的[l,r]与普通线段树不同!!普通线段树中是控制的范围,动态开点线段树是左右儿子的节点号!
|
|
|
// 动态开点线段树中u节点的控制范围是通过函数的2,3两个参数传递过去的,不是记录在tr[u].l,tr[u].r中的!记录在tr[u].l,tr[u].r中的是左右儿子的节点号!
|
|
|
// 理解清楚这一点非常重要,因为后面在求区间长度是,普通线段树len(u)=(tr[u].r-tr[u].l+1),而动态开点线段树len(u)=(r-l+1),len(ls)=mid-l+1,len(rs)=r-mid
|
|
|
int mu, add; // 乘法标记,加法标记
|
|
|
LL sum; // 区间和
|
|
|
} tr[N << 1];
|
|
|
|
|
|
int root, idx;
|
|
|
|
|
|
// 汇总
|
|
|
void pushup(int u) {
|
|
|
tr[u].sum = (tr[ls].sum + tr[rs].sum) % p;
|
|
|
}
|
|
|
|
|
|
// 创建节点:节点号分配,懒标记初始化
|
|
|
void build(int &u) {
|
|
|
if (u) return;
|
|
|
u = ++idx;
|
|
|
tr[u].add = 0;
|
|
|
tr[u].mu = 1;
|
|
|
}
|
|
|
|
|
|
void pushdown(int &u, int l, int r) {
|
|
|
if (tr[u].add == 0 && tr[u].mu == 1) return; // 默认懒标记
|
|
|
|
|
|
build(ls), build(rs); // 左儿子创建, 右儿子创建
|
|
|
|
|
|
int &mu = tr[u].mu, &add = tr[u].add; // 此时的add懒标记,已经处理过了
|
|
|
tr[ls].sum = ((LL)mu * tr[ls].sum % p + (LL)(mid - l + 1) * add % p) % p;
|
|
|
tr[rs].sum = ((LL)mu * tr[rs].sum % p + (LL)(r - mid) * add % p) % p;
|
|
|
|
|
|
tr[ls].mu = (LL)tr[ls].mu * mu % p;
|
|
|
tr[rs].mu = (LL)tr[rs].mu * mu % p;
|
|
|
|
|
|
tr[ls].add = ((LL)tr[ls].add * mu % p + add) % p;
|
|
|
tr[rs].add = ((LL)tr[rs].add * mu % p + add) % p;
|
|
|
|
|
|
mu = 1, add = 0; // 清空懒标记
|
|
|
}
|
|
|
|
|
|
// 乘法
|
|
|
void mu(int &u, int l, int r, int L, int R, int v) {
|
|
|
build(u); // 动态开点
|
|
|
if (l >= L && r <= R) { // 如果区间被完整覆盖
|
|
|
tr[u].add = (LL)tr[u].add * v % p; // 比较重要的一步,add要在这里乘上v,因为后面可能要加其他的数而那些数其实是不用乘k的
|
|
|
tr[u].mu = (LL)tr[u].mu * v % p;
|
|
|
tr[u].sum = (LL)tr[u].sum * v % p;
|
|
|
return;
|
|
|
}
|
|
|
if (l > R || r < L) return; // 如果没有交集
|
|
|
|
|
|
// 下传懒标记
|
|
|
pushdown(u, l, r);
|
|
|
// 分裂
|
|
|
mu(ls, l, mid, L, R, v), mu(rs, mid + 1, r, L, R, v);
|
|
|
// 汇总
|
|
|
pushup(u);
|
|
|
}
|
|
|
|
|
|
// 加法
|
|
|
void add(int &u, int l, int r, int L, int R, int v) {
|
|
|
build(u); // 动态开点
|
|
|
if (l >= L && r <= R) { // 如果区间被完整覆盖
|
|
|
tr[u].add = ((LL)tr[u].add + v) % p;
|
|
|
tr[u].sum = ((LL)tr[u].sum + ((LL)v * ((LL)r - l + 1)) % p) % p;
|
|
|
return;
|
|
|
}
|
|
|
if (l > R || r < L) return; // 如果没有交集
|
|
|
|
|
|
// 下传懒标记
|
|
|
pushdown(u, l, r);
|
|
|
// 分裂
|
|
|
add(ls, l, mid, L, R, v), add(rs, mid + 1, r, L, R, v);
|
|
|
// 汇总
|
|
|
pushup(u);
|
|
|
}
|
|
|
|
|
|
// 区间查询
|
|
|
LL query(int u, int l, int r, int L, int R) {
|
|
|
if (l >= L && r <= R) return tr[u].sum; // 如果完整命中,返回我的全部
|
|
|
if (l > R || r < L) return 0; // 如果与我无关,返回0
|
|
|
pushdown(u, l, r);
|
|
|
return (query(ls, l, mid, L, R) % p + query(rs, mid + 1, r, L, R) % p) % p;
|
|
|
}
|
|
|
|
|
|
int main() {
|
|
|
// 文件输入输出
|
|
|
#ifndef ONLINE_JUDGE
|
|
|
freopen("P3373_2.in", "r", stdin);
|
|
|
#endif
|
|
|
// 加快读入
|
|
|
ios::sync_with_stdio(false), cin.tie(0);
|
|
|
cin >> n >> m >> p;
|
|
|
for (int i = 1; i <= n; i++) {
|
|
|
int x;
|
|
|
cin >> x;
|
|
|
add(root, 1, n, i, i, x);
|
|
|
}
|
|
|
|
|
|
while (m--) {
|
|
|
int op, l, r;
|
|
|
cin >> op >> l >> r;
|
|
|
if (op == 1) {
|
|
|
int k;
|
|
|
cin >> k;
|
|
|
mu(root, 1, n, l, r, k);
|
|
|
} else if (op == 2) {
|
|
|
int k;
|
|
|
cin >> k;
|
|
|
add(root, 1, n, l, r, k);
|
|
|
} else
|
|
|
printf("%lld\n", query(root, 1, n, l, r));
|
|
|
}
|
|
|
return 0;
|
|
|
} |