P3373 【模板】线段树 2 题解
前置知识:
简要题意:
维护数组的区间加,乘,区间和。
首先,如果没有乘的话,直接把 P3372 【模板】线段树1 的代码复制过来进行了。
那么,你会说:
- 那多简单,用两个标记,然后加的时候改加,乘的时候改乘。
真的是这样的吗?是还用我跟你讲啊
不是。
比方说,一个区间原来的和是 \(a\).假设给它依次打上 \(+2\),\(\times 3\),\(+4\),\(\times 5\) 的标记,并用 \(\text{mul}\) 表示乘法标记(初始为 \(1\)),\(\text{add}\) 表示 加法标记(初始为 \(0\)).
那么,按照你说的,应该是:
-
\(+2\),则 \(\text{add=2,mul=1}\).
-
\(\times 3\),则 \(\text{add=2,mul=3}\).
好了,这里就已经错了。你 \(\times 3\) 的时候,其实结果应该是:
\((a+2) \times 3 = 3a + 6\),所以你的加法标记 碰到加法是累加,碰到乘法是累乘!
那么,计算优先级呢?当然是先乘法啦。
因为,如果 \(a \times 3 + 2\) 的话,你先算加法就是 \((a+2) \times 3\),然后就错了。因为你 根据乘法分配律把加法已经维护完了,最后一定是 \(a \times mul + add\) 的形式。
然后,标记的下传有一些细节。
另附一段对话(自编):
-
线段树:唉我真是烦死了,标记要是全下传了不就不如你了吗,嗯?
-
暴力:那怎么行,你可是 提高组算法,我是入门组算法啊,你不要下传标记就完了呗。
-
线段树:嗯?那怎么行?
-
暴力:没有人来询问它,你就别下传啊。等到有人询问的时候,你再下传一步,反正又不影响?
-
线段树:真好。可两个加、乘标记混合了怎么办啊。。。
-
暴力:你应该。。
@#$^&%!$# ……*&%¥&@34%#!
\(\cdots \cdots \cdots\) -
线段树:我 \(***\)!!!偷懒都这么难的么。。
-
暴力:你不难,你还是提高组算法么?看好了,这可是绿题!要是 我能解决还要你干嘛呢?是不是?
-
线段树:行,我先脑补一下。。。
-
暴力:
要不是 CCF 老年机一秒跑不了 \(10^{12}\) 次,我还要你线段树????
时间复杂度:\(O(n \log n)\).
实际得分:\(100pts\).
//与线段树 1 相同的部分不再注释
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define L (i<<1)
#define R (i<<1)+1
typedef long long ll;
inline ll read(){char ch=getchar();ll f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
ll x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
const ll N=1e5+1;
ll n,m,MOD;
struct node{
ll l,r,sum;
ll mul,add;
};
node t[4*N]; ll a[N];
inline void update(ll i) {
t[i].sum=(t[L].sum+t[R].sum)%MOD;
}
inline void build_tree(ll i,ll l,ll r) {
t[i].l=l; t[i].r=r; t[i].mul=1;
if(l==r) {t[i].sum=a[l]%MOD;return;}
ll mid=(l+r)>>1;
build_tree(L,l,mid);
build_tree(R,mid+1,r);
update(i);
}
inline void pushdown(ll i) {
t[L].sum=(t[i].mul*t[L].sum+((t[L].r-t[L].l+1)*t[i].add)%MOD)%MOD;
t[R].sum=(t[i].mul*t[R].sum+((t[R].r-t[R].l+1)*t[i].add)%MOD)%MOD; //根据区间长度更新区间和
t[L].mul=(t[L].mul*t[i].mul)%MOD;
t[R].mul=(t[R].mul*t[i].mul)%MOD; //乘法标记累乘
t[L].add=(t[i].mul*t[L].add+t[i].add)%MOD;
t[R].add=(t[i].mul*t[R].add+t[i].add)%MOD; //加法标记先乘后加
t[i].mul=1; t[i].add=0; //记得甩锅
}
inline void change_add(ll i,ll l,ll r,ll k) {
if(l<=t[i].l && t[i].r<=r) {
t[i].add=(t[i].add+k)%MOD;
t[i].sum=(t[i].sum+k*(t[i].r-t[i].l+1))%MOD;
return; //更新区间和
} pushdown(i); update(i);
ll mid=(t[i].l+t[i].r)>>1;
if(l<=mid) change_add(L,l,r,k);
if(r>mid) change_add(R,l,r,k);
update(i);
}
inline void change_mul(ll i,ll l,ll r,ll k) {
if(l<=t[i].l && t[i].r<=r) {
t[i].add=(t[i].add*k)%MOD;
t[i].mul=(t[i].mul*k)%MOD; //更新区间乘
t[i].sum=(t[i].sum*k)%MOD; return;
} pushdown(i); update(i);
ll mid=(t[i].l+t[i].r)>>1;
if(l<=mid) change_mul(L,l,r,k);
if(r>mid) change_mul(R,l,r,k);
update(i);
}
inline ll querysum(ll i,ll l,ll r) {
if(l<=t[i].l && t[i].r<=r) return t[i].sum;
pushdown(i);
ll mid=(t[i].l+t[i].r)>>1,ans=0;
if(l<=mid) ans=(ans+querysum(L,l,r))%MOD;
if(r>mid) ans=(ans+querysum(R,l,r))%MOD;
return ans;
}
int main(){
n=read(),m=read(),MOD=read();
for(ll i=1;i<=n;i++) a[i]=read();
build_tree(1,1,n); while(m--) {
ll op=read(),x=read(),y=read(),k;
if(op==1) k=read(),change_mul(1,x,y,k);
if(op==2) k=read(),change_add(1,x,y,k);
if(op==3) printf("%lld\n",querysum(1,x,y));
}
return 0;
}