洛谷P3373 [ 模板] 线段树 (乘法和加法)

       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,日后更新其他的图论,数据结构等内容,就酱 :)




   


你可能感兴趣的:(模板库)