//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 在查詢和修改操作時不變化,即可以使用結構體最佳化函式參量