【bzoj 1798】【luogu p3373】线段树模板题
- 序列a1,a2,…,aN。有如下三种操作形式:
- (1)把数列中的一段数全部乘一个值;
- (2)把数列中的一段数全部加一个值;
- (3)询问数列中的一段数的和。
- 由于答案可能很大,你只需输出这个数模P的值。
【分析】线段树,维护两个标记,加为 tmp_1,乘为 tmp_2。
加x的操作变为:乘1+x ; 乘c的操作变为:乘c+0 。
设当前区间长度为 len,当前区间需要乘 c 加 x,
当前区间原有标记为 c′ 和 x′,当前区间和为 sum 。
很明显:sum=c∗sum+x∗len
如何更新 c′ 和 x′ 呢? 设原区间和为 sum′ 。
sum=c∗sum+x∗len=c∗(c′∗sum′+x′∗len)+x∗len
所以 sum=c∗c′∗sum′+(c∗add′+add)∗len
这样就得出:c′=c∗c′ , x′=c∗x′+x 。
下传标记时,先下传乘法标记,再下传加法标记 。
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
/*【bzoj 1798】序列a1,a2,…,aN。有如下三种操作形式:
(1)把数列中的一段数全部乘一个值; (2)把数列中的一段数全部加一个值;
(3)询问数列中的一段数的和。由于答案可能很大,你只需输出这个数模P的值。*/
#define MAXN 100005
struct tree_type{
int l,r;
LL tmp_1,tmp_2,sum;
}t[MAXN*4];
int n,m,p,opt,L,R,c,a[MAXN];
void build(int num,int l,int r){ //建树
t[num].l=l; t[num].r=r;
t[num].tmp_1=0; t[num].tmp_2=1;
if(l==r){ t[num].sum=a[l]%p; return; }
int mid=(l+r)/2; build(num*2,l,mid); build(num*2+1,mid+1,r);
t[num].sum=(t[num*2].sum+t[num*2+1].sum)%p;
return;
}
void push_down(int num){ //标记下移
if(t[num].tmp_2==1&&t[num].tmp_1==0) return; //+0并且*1,不影响
t[num*2].tmp_1=(t[num*2].tmp_1*t[num].tmp_2+t[num].tmp_1)%p; //加法标记
t[num*2+1].tmp_1=(t[num*2+1].tmp_1*t[num].tmp_2+t[num].tmp_1)%p;
t[num*2].tmp_2=(t[num*2].tmp_2*t[num].tmp_2)%p; //乘法标记
t[num*2+1].tmp_2=(t[num*2+1].tmp_2*t[num].tmp_2)%p;
t[num*2].sum=(t[num*2].sum*t[num].tmp_2+
(t[num*2].r-t[num*2].l+1)*t[num].tmp_1)%p;
t[num*2+1].sum=(t[num*2+1].sum*t[num].tmp_2+
(t[num*2+1].r-t[num*2+1].l+1)*t[num].tmp_1)%p; //标记记入总和中
t[num].tmp_1=0; t[num].tmp_2=1; return; //记得归零
}
void add(int num,int l,int r,int x){ //加法
if(t[num].l>r||t[num].r<l) return;
if(t[num].l>=l&&t[num].r<=r){
t[num].tmp_1=(t[num].tmp_1+x)%p; //标记
t[num].sum=(t[num].sum+(t[num].r-t[num].l+1)*x)%p;
return;
}
push_down(num); //标记下移
add(num*2,l,r,x); add(num*2+1,l,r,x);
t[num].sum=(t[num*2].sum+t[num*2+1].sum)%p;
return;
}
void multiply(int num,int l,int r,int x){ //乘法
if(t[num].l>r||t[num].r<l) return;
if(t[num].l>=l&&t[num].r<=r){
t[num].tmp_1=(t[num].tmp_1*x)%p;
t[num].tmp_2=(t[num].tmp_2*x)%p;
t[num].sum=(t[num].sum*x)%p;
return;
}
push_down(num); //标记下移
multiply(num*2,l,r,x); multiply(num*2+1,l,r,x);
t[num].sum=(t[num*2].sum+t[num*2+1].sum)%p;
return;
}
LL ask(int num,int l,int r){ //区间查询
if(t[num].l>r||t[num].r<l) return 0; //无重叠
if(t[num].l>=l&&t[num].r<=r) return t[num].sum%p; //全包含
push_down(num); //标记下移
return (ask(num*2,l,r)+ask(num*2+1,l,r))%p; //递归左右子树
}
int main(){
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build(1,1,n);
for(int i=1;i<=m;i++){
scanf("%d",&opt);
if(opt==1){
scanf("%d%d%d",&L,&R,&c);
multiply(1,L,R,c); //乘法
}
if(opt==2){
scanf("%d%d%d",&L,&R,&c);
add(1,L,R,c); //加法
}
if (opt==3){
scanf("%d%d",&L,&R);
printf("%lld\n",ask(1,L,R)%p); //求和
}
}
return 0;
}
——时间划过风的轨迹,那个少年,还在等你。