线段树解决区间乘与区间加
原题 维护序列
这里乘和加的修改原理如下
原来区间里的每一个数为(a*mul+add) (a为没pushdown懒标记前的数,mul为乘懒标记,add为加的懒标记)
然后对原来区间每一个数乘上c,加上d,即(a * mul+add) * c+d --> a * mul * c +add * c+d
这样原来的乘标记就变为mul * c,加标记就变为add * c+d;
当我们执行区间乘时,令d=0,区间和操作时令c=1即可
#include<iostream>
using namespace std;
const int N=1e5+5;
typedef long long LL;
int w[N],n,m,p;
struct Tree
{
int l,r;
int sum,add,mul;
}tr[N<<2];
void pushup(int u)
{
tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}
void eval(Tree &t,int add,int mul)//我们把乘和除都写到一个函数里面
{
t.sum=((LL)t.sum*mul+(LL)(t.r-t.l+1)*add)%p;
t.mul=(LL)t.mul*mul%p;
t.add=((LL)t.add*mul+add)%p;
//这里都涉及乘法,都要转为long long和模上p
}
void pushdown(int u)
{
eval(tr[u<<1],tr[u].add,tr[u].mul);
eval(tr[u<<1|1],tr[u].add,tr[u].mul);
tr[u].add=0,tr[u].mul=1;
}
void build(int u,int l,int r)
{
if (l==r) tr[u]={l,r,w[r],0,1};
else {
tr[u]={l,r,0,0,1};
int mid=tr[u].l+tr[u].r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
}
//我们的修改函数只写一个就行,当区间乘上一个数时,令add=0,当区间需要加上一个数时,令mul=1即可
void modify(int u,int l,int r,int add,int mul)
{
if (l<=tr[u].l&&r>=tr[u].r) eval(tr[u],add,mul);
else {
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if (l<=mid) modify(u<<1,l,r,add,mul);
if (r>mid) modify(u<<1|1,l,r,add,mul);
pushup(u);
}
}
LL query(int u,int l,int r)
{
if (l<=tr[u].l&&r>=tr[u].r) return tr[u].sum;
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
LL sum=0;
if (l<=mid) sum=(sum+query(u<<1,l,r))%p;
if (r>mid) sum=(sum+query(u<<1|1,l,r))%p;//这里注意都要模上p,每个sum都有可能爆int
return sum;
}
int main()
{
cin>>n>>p;
for (int i=1;i<=n;i++) cin>>w[i];
build(1,1,n);
int op,l,r,d;
cin>>m;
while (m--)
{
cin>>op>>l>>r;
if (op==1) {
cin>>d;
modify(1,l,r,0,d);
}
else if (op==2) {
cin>>d;
modify(1,l,r,d,1);
}
else if (op==3) {
cout<<query(1,l,r)<<"\n";
}
}
return 0;
}