题目描述
如题,已知一个数列,你需要进行下面三种操作:
将某区间每一个数乘上 x
将某区间每一个数加上 x
求出某区间每一个数的和
输入格式
第一行包含三个整数 n,m,p,分别表示该数列数字的个数、操作的总个数和模数。
第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。
接下来 m 行每行包含若干个整数,表示一个操作,具体如下:
操作 1: 格式:1 x y k 含义:将区间 [x,y]内每个数乘上 k
操作 2: 格式:2 x y k 含义:将区间 [x,y]内每个数加上 k
操作 3: 格式:3 x y 含义:输出区间[x,y]内每个数的和对 p 取模所得的结果
输出格式
输出包含若干行整数,即为所有操作 3 的结果。
1.区间既有加法又有乘法,直接的乘法线段树显然就不行了,因为先加后乘和先乘后加绝对不一样。这时我们需要设置两个标记,分别是乘法标记和加法标记:
typedef long long ll;
const int N=1e5+10;
ll a[N];
struct tree{
ll l,r;
ll sum;
ll p_lazy,m_lazy; //前者为加法标记,后者为乘法标记
tree(){ l=r=sum=p_lazy=0; m_lazy=1; }
}sgt[N<<2];
2.在写mutiply函数之前我们显然需要先考虑pushdown,下传时显然两个标记同时下传,这里我们会想,是先乘后加还是先加后乘呢?我们推一下,如果是已经有了乘法标记我们再设置加法标记的话,显然对其他没有影响,但是如果我们已经有了加法标记再设置了乘法标记的话,为了得到正确的结果需要更新加法标记。
3.所以更新左右区间的两个标记时要注意:更新乘法标记直接将父区间的乘法标记乘给子区间,而更新加法标记时需要将父区间的乘法标记乘以原来的加法标记再加上父区间的加法标记
void pushdown(ll i){
ll t1=sgt[i].m_lazy,t2=sgt[i].p_lazy;
ll k=i<<1;
//下面这两步就是将add函数和mutiply函数改变sum的方式结合起来
sgt[k].sum=sgt[k].sum*t1+t2*(sgt[k].r-sgt[k].l+1);
sgt[k|1].sum=sgt[k|1].sum*t1+t2*(sgt[k|1].r-sgt[k|1].l+1);
//下面这四步相当于add函数和mutiply函数改变两种标记的方式结合起来
sgt[k].m_lazy=sgt[k].m_lazy*t1;
sgt[k|1].m_lazy=sgt[k|1].m_lazy*t1;
sgt[k].p_lazy=sgt[k].p_lazy*t1+t2;
sgt[k|1].p_lazy=sgt[k|1].p_lazy*t1+t2;
//初始化两种标记
sgt[i].m_lazy=1;
sgt[i].p_lazy=0;
return;
}
4.然后由于我们刚才pushdown就得出了应该先乘后加,这样的话设区间sum值为x,加法标记为y,新增加的乘法标记为z。由(x+y)*z,所以我们的multiply更新区间的加法标记就是需要直接乘新的乘法标记
void multiply(ll i,ll l,ll r,ll x){
if(sgt[i].l==l&&sgt[i].r==r){
sgt[i].sum=x*sgt[i].sum;
sgt[i].m_lazy=x*sgt[i].m_lazy;
sgt[i].p_lazy=x*sgt[i].p_lazy;
return;
}
pushdown(i);
ll mid=(sgt[i].l+sgt[i].r)>>1;
ll k=i<<1;
if(r<=mid) multiply(k,l,r,x);
else if(l>mid) multiply(k|1,l,r,x);
else multiply(k,l,mid,x),multiply(k|1,mid+1,r,x);
sgt[i].sum=sgt[k].sum+sgt[k|1].sum;
return;
}
5.题目中还要求对结果取模,我们在初始化时,每次更新sum时,更新两种标记时还有求sum时都要取模,因为加法和乘法对分解的所有子过程取模也就达到了对整个结果取模的目的
代码:
#include <iostream>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll a[N];
struct tree{
ll l,r;
ll sum;
ll p_lazy,m_lazy;
tree(){ l=r=sum=p_lazy=0; m_lazy=1; }
}sgt[N<<2];
ll n,m,p;
void build(ll i,ll l,ll r){
sgt[i].l=l;
sgt[i].r=r;
if(l==r){
sgt[i].sum=a[l]%p;
return;
}
ll mid=(l+r)>>1;
ll k=i<<1;
build(k,l,mid);
build(k|1,mid+1,r);
sgt[i].sum=(sgt[k].sum%p+sgt[k|1].sum%p)%p;
return;
}
void pushdown(ll i){
ll t1=sgt[i].m_lazy,t2=sgt[i].p_lazy;
ll k=i<<1;
sgt[k].sum=(sgt[k].sum*t1+t2*(sgt[k].r-sgt[k].l+1))%p;
sgt[k|1].sum=(sgt[k|1].sum*t1+t2*(sgt[k|1].r-sgt[k|1].l+1))%p;
sgt[k].m_lazy=(sgt[k].m_lazy*t1)%p;
sgt[k|1].m_lazy=(sgt[k|1].m_lazy*t1)%p;
sgt[k].p_lazy=(sgt[k].p_lazy*t1+t2)%p;
sgt[k|1].p_lazy=(sgt[k|1].p_lazy*t1+t2)%p;
sgt[i].m_lazy=1;
sgt[i].p_lazy=0;
return ;
}
void add(ll i,ll l,ll r,ll x){
if(sgt[i].l==l&&sgt[i].r==r){
sgt[i].sum+=(x*(sgt[i].r-sgt[i].l+1))%p;
sgt[i].p_lazy=(x+sgt[i].p_lazy)%p;
return;
}
pushdown(i);
ll mid=(sgt[i].l+sgt[i].r)>>1;
ll k=i<<1;
if(r<=mid) add(k,l,r,x);
else if(l>mid) add(k|1,l,r,x);
else add(k,l,mid,x),add(k|1,mid+1,r,x);
sgt[i].sum=(sgt[k].sum+sgt[k|1].sum)%p;
return;
}
void multiply(ll i,ll l,ll r,ll x){
if(sgt[i].l==l&&sgt[i].r==r){
sgt[i].sum=(x*sgt[i].sum)%p;
sgt[i].m_lazy=(x*sgt[i].m_lazy)%p;
sgt[i].p_lazy=(x*sgt[i].p_lazy)%p;
return;
}
pushdown(i);
ll mid=(sgt[i].l+sgt[i].r)>>1;
ll k=i<<1;
if(r<=mid) multiply(k,l,r,x);
else if(l>mid) multiply(k|1,l,r,x);
else multiply(k,l,mid,x),multiply(k|1,mid+1,r,x);
sgt[i].sum=(sgt[k].sum+sgt[k|1].sum)%p;
return;
}
ll query(ll i,ll l,ll r){
if(sgt[i].l==l&&sgt[i].r==r)
return sgt[i].sum;
pushdown(i);
ll mid=(sgt[i].l+sgt[i].r)>>1;
ll k=i<<1;
if(r<=mid) return query(k,l,r)%p;
else if(l>mid) return query(k|1,l,r)%p;
else return query(k,l,mid)%p+query(k|1,mid+1,r)%p;
}
int main()
{
ll op,x,y,k;
scanf("%lld %lld %lld",&n,&m,&p);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
build(1,1,n);
while(m--){
scanf("%lld",&op);
if(op==1){
scanf("%lld %lld %lld",&x,&y,&k);
multiply(1,x,y,k);
}else if(op==2){
scanf("%lld %lld %lld",&x,&y,&k);
add(1,x,y,k);
}else{
scanf("%lld %lld",&x,&y);
printf("%lld\n",query(1,x,y)%p);
}
}
return 0;
}