andy的小伙伴acer(WA_哈_哈)已经写好【模板】线段树1啦,但是仅仅支持区间加法和查询,这对
于oier们当然是远远不够的,所以本蒟蒻在此奉上线段树的区间加法,乘法的实现,以及对乘法标记的下放,
查询的实现;
解释下标记下放要先乘后加的原因:(我们约定 lazy1 为加法标记,lazy2 为乘法标记)
设有区间 [x,y] 现执行操作:将该区间内所有值 加 n;
[x,y] -> [ax+n....ay+n];在线段树里则表示为 该区间父节点的lazy1标记 +n,即为:[x,y] -> [ax..ay](lazy1 + n);
如果此时 再进行操作 将该区间内 所有值 乘 m;
[x,y] -> [ax*m....ay*m];在线段树里则表示为 该区间父节点的lazy2标记*m,即为:[x,y]->[ax..ay](lazy2*m);
但是 lazy1 标记呢?
我们来看,原区间经过2次操作后,应变为-> [(ax+n)*m...a(ay+n)*m] -> [ax*m+m*n...ay*m+m*n];
由此可见,加法标记(lazy1)变为了m*n;
那为什么要先乘后加呢,现在看起来先加再乘也没什么问题;
在线段树实现里,我们每进行一次操作,实际上等于对该 若干区间的父节点上(以及父区间)运
算,所以 lazy1,lazy2是分开运算的,在下放标记时,会先对lazy1*lazy2,再向下下放,对子区间而言,
会先得 到乘法标记,再得到加法标记,所以先乘后加。
其实,先加后乘也不是不可以,但是子区间节点也需要继承父亲的 lazy1,lazy2,先乘后加就不用对子区
间的lazy1,再乘 lazy2;同时要记得下放标记后,父节点的标记重新置为 lazy1=0,lazy2=1;
以及在此附上acer的链接(他的是结构体实现),看各位喜好啦:
http://blog.csdn.net/acerandaker/article/details/79371464
#include
using namespace std;
#define M 100050
int m,n,p,xx,yy,zz,s;//本代码%p是因为题目需求,直接去掉就好,1ll* 是强转long long,add是加法标记,mul是乘法标记;
int tree[M<<2],mul[M<<2],add[M<<2];//tree :树的加和|mul乘法懒标记|add加法懒标记
void build(int pos,int l,int r){
mul[pos]=1;
if(l==r){//叶子节点
scanf("%d",&tree[pos]);
return ;//Online 建树
}
int mid=(l+r)>>1,lson=pos<<1,rson=pos<<1|1;//等价于 mid=(l+r)/2,lson=pos/2,rson=pos/2+1;
build(lson,l,mid),build(rson,mid+1,r);//递归建树
tree[pos]=(1ll*tree[lson]+tree[rson])%p;//更新父节点
}
void pushdown(int pos,int l,int r){//下放标记,无论乘法还是加法都遵循 先乘后加 的原则
int mid=(l+r)>>1,lson=pos<<1,rson=pos<<1|1;//等价于 mid=(l+r)/2,lson=pos/2,rson=pos/2+1;
tree[lson]=1ll*tree[lson]*mul[pos]%p,tree[rson]=1ll*tree[rson]*mul[pos]%p;
mul[lson]=1ll*mul[lson]*mul[pos]%p,mul[rson]=1ll*mul[rson]*mul[pos]%p;
add[lson]=1ll*add[lson]*mul[pos]%p,add[rson]=1ll*add[rson]*mul[pos]%p;
tree[lson]=(tree[lson]+1ll*add[pos]*(mid-l+1))%p,tree[rson]=(tree[rson]+1ll*add[pos]*(r-mid))%p;
add[lson]=(add[pos]+1ll*add[lson])%p,add[rson]=(add[pos]+1ll*add[rson])%p;
mul[pos]=1,add[pos]=0;//一定是mul=1;不然在对下一次做乘法标记时会出问题;
}
int query(int pos,int l,int r,int L,int R){//查询区间和运算,
if(L<=l&&r<=R){return tree[pos];}//如果完全包含,则取查找的区间的值
pushdown(pos,l,r);//这就是我们打标记的目的,因为操作时直接运算,复杂度会爆掉
int mid=(l+r)>>1,lson=pos<<1,rson=pos<<1|1;
if(L>mid){return query(rson,mid+1,r,L,R)%p; }
if(mid>=R){return query(lson,l,mid,L,R)%p;}
else return (1ll*query(lson,l,mid,L,R)+query(rson,mid+1,r,L,R))%p; //左右递归查询,罗列各小区间,拼凑目标区间
}
void update(int pos,int l,int r,int c,int L,int R){//加法运算
if(L<=l&&r<=R){
tree[pos]=(tree[pos]+1ll*c*(r-l+1))%p;
add[pos]=(add[pos]+1ll*c)%p;
return;
}
pushdown (pos,l,r);
int mid=(l+r)>>1,lson=pos<<1,rson=pos<<1|1;
if(mid=R)update(lson,l,mid,c,L,R);//完全超出右边,往左动动
else update(lson,l,mid,c,L,R),update(rson,mid+1,r,c,L,R);//不然,在区间内很大一部分但是不完全(有可能)覆盖
tree[pos]=(1ll*tree[lson]+tree[rson])%p; // pushup 一下
}
void update2(int pos,int l,int r,int x,int L,int R){//乘法运算
if(L<=l&&r<=R){
tree[pos]=1ll*tree[pos]*x%p;
mul[pos]=1ll*mul[pos]*x%p;
add[pos]=1ll*add[pos]*x%p;//一起改掉加法标记
return;
}
pushdown(pos,l,r);
int mid=(l+r)>>1,lson=pos<<1,rson=pos<<1|1;
if(mid=R) update2(lson,l,mid,x,L,R);
else update2(rson,mid+1,r,x,L,R),update2(lson,l,mid,x,L,R);
tree[pos]=(1ll*tree[rson]+tree[lson])%p;
}
int main(void){
scanf("%d%d%d",&n,&m,&p),build(1,1,n);
while(m--){
scanf("%d%d%d",&s,&xx,&yy);
if(s==3)printf("%d\n",query(1,1,n,xx,yy));
else scanf("%d",&zz),s==1?update2(1,1,n,zz,xx,yy):update(1,1,n,zz,xx,yy);
}
}
题目原型:洛谷P3373,【模板】线段树2,日后更新其他的图论,数据结构等内容,就酱 :)