题目链接:【模板】线段树 2 - 洛谷
这题总共有三种操作:
-
将某区间每一个数乘上 x
-
将某区间每一个数加上 x
-
求出某区间每一个数的和
add和multi这两种修改,在节点上分别使用两个Lazy-Tag标记,这两个标记的关系如下:
- 做add修改时,跟普通标记一样,没什么特别需要注意的地方
- 做multi修改时,如果原先有add标记,将add标记改写为 add*multi,这个时候的子树的值就可以由 子树的值=子树的值*父节点的multi的标记数+父节点的add标记数*区间大小 得到
其中具体函数的操作代码如下:
- 做线段树的pushdown操作时,需要注意,先进行子区间的修改,然后进行add和multi操作的下传,具体操作看下面代码:
void pushdown(int p,int lp,int rp) { int mid=(lp+rp)>>1; sum[ls(p)]=sum[ls(p)]*multi[p]+add[p]*(mid-lp+1);//左子树上的值变为原来的值*父节点的multi标记+父节点的add标记*区间大小 sum[rs(p)]=sum[rs(p)]*multi[p]+add[p]*(rp-mid);//右子树同上 multi[ls(p)]=multi[ls(p)]*multi[p]; multi[rs(p)]=multi[rs(p)]*multi[p]; add[ls(p)]=add[ls(p)]*multi[p]+add[p];//左子树上的add标记变为(add*父节点的multi标记+父节点的add标记) add[rs(p)]=add[rs(p)]*multi[p]+add[p];//右子树同上 multi[p]=1;//乘的标记初值为1 add[p]=0; }
ls函数和rs函数如下(求左右节点位置的函数):
int ls(int p) { return p<<1; } int rs(int p) { return p<<1|1; }
求区间和的操作代码:
int query(int x,int y,int p,int lp,int rp) { if(x<=lp&&y>=rp)//当该区间处于所求区间中间时 return sum[p]; pushdown(p,lp,rp); int mid=(lp+rp)>>1; long long ans=0; if(x<=mid) ans+=query(x,y,ls(p),lp,mid); if(y>mid) ans+=query(x,y,rs(p),mid+1,rp); return ans; }
- 将区间上每个点都加上x:
void update1(int x,int y,int k,int p,int lp,int rp) { if(x<=lp&&y>=rp) { add[p]+=k; sum[p]=sum[p]+k*(rp-lp+1); return; } pushdown(p,lp,rp); int mid=(lp+rp)>>1; if(x<=mid) update1(x,y,k,ls(p),lp,mid); if(y>mid) update1(x,y,k,rs(p),mid+1,rp); pushup(p); }
- 将区间上每个点都乘上x:
void update2(int x,int y,int k,int p,int lp,int rp) { if(x<=lp&&y>=rp) { sum[p]=sum[p]*k; multi[p]=multi[p]*k; add[p]=add[p]*k;//这里就需要变为add*k,便于子节点的计算与传递 return; } pushdown(p,lp,rp); int mid=(lp+rp)>>1; if(x<=mid) update2(x,y,k,ls(p),lp,mid); if(y>mid) update2(x,y,k,rs(p),mid+1,rp); pushup(p); }
上述代码只是为了做一个简单讲述,因为题目要求有取模运算,以及可想而知的数肯定很大,所以需要开long long类型。
具体ac代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#define ll long long
using namespace std;
long long add[100005<<2]={0},sum[100005<<2],multi[100005<<2]={0};
long long arr[100005];
ll n,m,mod;
ll ls(ll p) { return p<<1; }
ll rs(ll p) { return p<<1|1; }
void pushup(ll p){
sum[p]=sum[ls(p)]+sum[rs(p)];
sum[p]%=mod;
}
void build(ll p,ll lp,ll rp)
{
multi[p]=1;
if(lp==rp)
{
sum[p]=arr[lp];
return ;
}
ll mid=(lp+rp)>>1;
build(ls(p),lp,mid);
build(rs(p),mid+1,rp);
pushup(p);
}
void pushdown(ll p,ll lp,ll rp)
{
ll mid=(lp+rp)>>1;
sum[ls(p)]=(sum[ls(p)]*multi[p]+add[p]*(mid-lp+1))%mod;
sum[rs(p)]=(sum[rs(p)]*multi[p]+add[p]*(rp-mid))%mod;
multi[ls(p)]=(multi[ls(p)]*multi[p])%mod;
multi[rs(p)]=(multi[rs(p)]*multi[p])%mod;
add[ls(p)]=(add[ls(p)]*multi[p]+add[p])%mod;
add[rs(p)]=(add[rs(p)]*multi[p]+add[p])%mod;
multi[p]=1;
add[p]=0;
}
void update1(ll x,ll y,ll k,ll p,ll lp,ll rp)
{
if(x<=lp&&y>=rp)
{
add[p]+=k;
sum[p]=(sum[p]+k*(rp-lp+1))%mod;
return;
}
pushdown(p,lp,rp);
ll mid=(lp+rp)>>1;
if(x<=mid) update1(x,y,k,ls(p),lp,mid);
if(y>mid) update1(x,y,k,rs(p),mid+1,rp);
pushup(p);
}
void update2(ll x,ll y,ll k,ll p,ll lp,ll rp)
{
if(x<=lp&&y>=rp)
{
sum[p]=(sum[p]*k)%mod;
multi[p]=multi[p]*k%mod;
add[p]=(add[p]*k)%mod;
return;
}
pushdown(p,lp,rp);
ll mid=(lp+rp)>>1;
if(x<=mid) update2(x,y,k,ls(p),lp,mid);
if(y>mid) update2(x,y,k,rs(p),mid+1,rp);
pushup(p);
}
long long query(ll x,ll y,ll p,ll lp,ll rp)
{
if(x<=lp&&y>=rp)
return sum[p];
pushdown(p,lp,rp);
ll mid=(lp+rp)>>1;
long long ans=0;
if(x<=mid) ans+=query(x,y,ls(p),lp,mid);
if(y>mid) ans+=query(x,y,rs(p),mid+1,rp);
return ans;
}
int main(){
scanf("%lld %lld %lld",&n,&m,&mod);
for(int i=1;i<=n;i++)
scanf("%lld",&arr[i]);
build(1,1,n);
while(m--)
{
ll num,x,y,k;
scanf("%lld%lld%lld",&num,&x,&y);
if(num==1){
scanf("%lld",&k);
update2(x,y,k,1,1,n);
}
else if(num==2){
scanf("%lld",&k);
update1(x,y,k,1,1,n);
}
else
printf("%lld\n",query(x,y,1,1,n)%mod);
}
return 0;
}
光看果然是不行的,实践是检验真理的唯一标准果然没错,学了好久的线段树了,结果这题我还是写了半天,到处找资料,属实是没脸见人了,果然还是得多刷题啊,希望以后越来越好qwq