zoukankan      html  css  js  c++  java
  • 【题解】P3373 【模板】线段树 2

    线段树解法

    好丢脸,这个题做了一下午,调试了三个多小时......

    先讲讲解题思路

    既然这里是线段树,就要用到lazy—tag。又有加法又有乘法的话,就要用到两个lazy-tag,分别用数组jia[]和chng[]表示。线段树用数组t[]存。

    我们让lazy-tag还原数值时,先乘chng[],再加jia[](人为规定,这样好算)

    怎么维护lazy-tag?

    加法

    void add( k, l, r, x, y, delta)
    {

    函数的作用是在编号为k,区间是[l,r]的线段树里,给区间[x,y]里的每一个数加上delta。
    
    如果当前区间[l,r]和目标区间[x,y]完全重合,就要在当前这颗编号为k的树上标记。
    	首先jia[k]要加上delta,表示当前区间[l,r](即[x,y])内的每一个数都加了delta;
    	然后要修改t[k]的值,也就是加上区间内增加的总数,即t[k]+=delta*(r-l+1);
    	return。
    如果当前区间不与目标区间完全重合,就要对子树操作。
    	首先,标记下传,用pushdown()函数将树k的标记全数下传给两个儿子k*2和k*2+1;
    	然后,先取mid=(l+r)>>1,判断一下目标区间是在当前区间的左子树区间、还是右子树区间、还是左右都有;
    		(如果y<=mid,那么目标区间一定只在左子树里;如果x>=mid+1,那么目标区间一定只在右子树里;如果上述两个条件都不满足,那就是分布在左右区间了);
    	这样按分类递归子树,递归完了后标记就在子树里存好了,这样子树的值变了,接着别忘了更新t[k]的值(t[k]=t[k*2]+t[k*2+1]);
    	return。
    

    }

    这里要明确,当父亲k替儿子记录下了加法标记时,儿子啥值也没改,还是憨憨的和啥也没加一样。乘法也如此。

    这里是代码

    void add(long long k, long long l, long long r, long long x, long long y, long long delta)
    {
    	if(l==x&&r==y){
    		jia[k]+=delta;
    		t[k]+=delta*(r-l+1);
    		return;
    	}	
    	long long mid=(l+r)>>1;
    	pushdown(k,l,r);
    	if(x>=mid+1)	add(k*2+1,mid+1,r,x,y,delta); 
    	else if(y<=mid)	add(k*2,l,mid,x,y,delta);
    	else{
    		add(k*2,l,mid,x,mid,delta);
    		add(k*2+1,mid+1,r,mid+1,y,delta);
    	}
    	t[k]=t[k*2]+t[k*2+1];	
    		t[k]%=p;
    }
    

    乘法

    void cheng(k,l,r,x,y,ch)
    {

    给区间[x,y]里每一个数都乘ch
    给区间里的每一个数乘ch,如何标记在lazy-tag里?
    这个数啊,chng[]标记已经标记着他要乘chng[k],jia[]标记着他再要加jia[k],这下子又要乘ch,根据分配率,
    (t[k]*chng[k]+jia[k])*ch=t[k]*chng[k]*ch+jia[k]*ch
    这意味着标记乘ch时,我们要让chng[k]和jia[k]都乘上ch。
    具体操作时,还是要和加法一样分类处理,最后别忘更新t[k]。
    

    }
    代码

    void cheng(long long k, long long l, long long r, long long x, long long y, long long ch)
    {
    	if(l==x&&r==y){
    		t[k]*=ch;	
    			t[k]%=p;
    		chng[k]*=ch;
    			chng[k]%=p;
    		jia[k]*=ch;
    			jia[k]%=p;
    		return;
    	}
    	pushdown(k,l,r);
    	long long mid=(l+r)>>1;
    	if(x>=mid+1)	cheng(k*2+1,mid+1,r,x,y,ch);
    	else if(y<=mid)	cheng(k*2,l,mid,x,y,ch);
    	else {
    	cheng(k*2,l,mid,x,mid,ch);
    	cheng(k*2+1,mid+1,r,mid+1,y,ch);
    	}
    	t[k]=t[k*2]+t[k*2+1];
    		t[k]%=p;
    }
    

    pushdown

    维护lazy-tag需要pushdown,询问输出也要pushdown
    pushdown是标记下传操作,意味着当前树k不再保留所在区间的加乘标记,也就是说,pushdown后,jia[k]=0,chng[k]=1。
    下传到子树时,子树的加、乘标记和本身的值都要改变(终于不是憨憨的啥也不知道了!)。
    void pushdown(k,l,r)
    {

    更新左二子
    更新儿子的标记,和void cheng()维护乘法标记一样,用到分配率
    t[k*2]'->t[k*2]*chng[k*2]+jia[k*2],这里chng[k]表示区间内每一个数都要乘chng[k],
    所以t[k*2]'->(t[k*2]*chng[k*2]+jia[k*2])*chng[k]=t[k*2]*chng[k*2]*chng[k]+jia[k*2]*chng[k]
    这意味着chng[k*2]更新为chng[k*2]*chng[k],jia[k*2]更新为jia[k*2]*chng[k]。
    然后更新t[2*k]的值
    t[2*k]'->当前树的值*乘法标记+加法标记*区间长度
    
    更新完左右儿子后别忘还原父亲的标记
    

    }
    this is the code

    void pushdown(long long k, long long l, long long r)
    {
    	long long mid=(l+r)>>1;
    	jia[k*2]=jia[k*2]*chng[k]+jia[k];	
    		jia[k*2]%=p;
    	jia[k*2+1]=jia[k*2+1]*chng[k]+jia[k];
    	
    		jia[k*2+1]%=p;
    	chng[k*2]*=chng[k];
    		chng[k*2]%=p;
    	chng[k*2+1]*=chng[k];	
    		chng[k*2+1]%=p;
    
    	t[k*2]=t[k*2]*chng[k]+jia[k]*(mid-l+1);// 	cout<<t[k*2]<<" "<<chng[k]<<endl;
    		t[k*2]%=p;	
    	t[k*2+1]=t[k*2+1]*chng[k]+jia[k]*(r-mid);
    		t[k*2+1]%=p;
    		
    	jia[k]=0;
    	chng[k]=1;
    }
    

    建树

    这个大家都会,直接上代码

    void build(long long k, long long l, long long r)
    {
    	jia[k]=0;
    	chng[k]=1;//标记初始化
    	if(l==r){
    		t[k]=a[l];		//直接赋值
    		return;
    	}	
    	long long mid=(l+r)>>1;
    	build(k*2,l,mid);
    	build(k*2+1,mid+1,r);//递归建树
    	t[k]=(t[k*2]+t[k*2+1])%p;//给父亲赋值
    }
    

    询问

    void query(k,l,r,x,y)
    {

    还是分类讨论
    1.[l,r]和[x,y]完全重合,直接返回t[k]
    2.既然不完全重合,就先要pushdown,更新子树的值,再分类递归子树
    

    }
    code

    long long query(long long k, long long l, long long r, long long x, long long y)
    {
    	if(l==x && r==y){
    		return t[k]%p;
    	}
    	pushdown(k,l,r);
    	long long mid = (l+r)>>1;
    	if(x>=mid+1)	return query(k*2+1,mid+1,r,x,y)%p;
    	else	if(y<=mid)	return query(k*2,l,mid,x,y)%p;
    	else	return (query(k*2,l,mid,x,mid)+query(k*2+1,mid+1,r,mid+1,y))%p;
    }
    

    这样这个题大体就成了,然鹅,想要AC,还要注意以下三点

    1.数组开多大?其实要开n4,开n3也过了,但是开n*2就不行
    2.开longlong!开longlong!!
    3.随时取模,query返回时也要再取模

    代码
    #include <cmath>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <cstdlib>
    #include <iostream>
    #include <algorithm>
    #define N 100100
    
    //开longlong
    //开4倍 
    using namespace std;
    
    long long n,m,x,y,k,p,a[N],jia[N*4],chng[N*4],t[N*4];//开4倍 
    
    void build(long long k, long long l, long long r)
    {
    	jia[k]=0;
    	chng[k]=1;
    	if(l==r){
    		t[k]=a[l];		
    		return;
    	}	
    	long long mid=(l+r)>>1;
    	build(k*2,l,mid);
    	build(k*2+1,mid+1,r);
    	t[k]=(t[k*2]+t[k*2+1])%p;
    }
    
    void pushdown(long long k, long long l, long long r)
    {
    	long long mid=(l+r)>>1;
    	jia[k*2]=jia[k*2]*chng[k]+jia[k];	
    		jia[k*2]%=p;
    	jia[k*2+1]=jia[k*2+1]*chng[k]+jia[k];
    		jia[k*2+1]%=p;
    	chng[k*2]*=chng[k];
    		chng[k*2]%=p;
    	chng[k*2+1]*=chng[k];	
    		chng[k*2+1]%=p;
    	t[k*2]=t[k*2]*chng[k]+jia[k]*(mid-l+1);
    		t[k*2]%=p;	
    	t[k*2+1]=t[k*2+1]*chng[k]+jia[k]*(r-mid);
    		t[k*2+1]%=p;
    	jia[k]=0;
    	chng[k]=1;
    }
    
    void add(long long k, long long l, long long r, long long x, long long y, long long delta)
    {
    	if(l==x&&r==y){
    		jia[k]+=delta;
    		t[k]+=delta*(r-l+1);
    		return;
    	}	
    	long long mid=(l+r)>>1;
    	pushdown(k,l,r);
    	if(x>=mid+1)	add(k*2+1,mid+1,r,x,y,delta); 
    	else if(y<=mid)	add(k*2,l,mid,x,y,delta);
    	else{
    		add(k*2,l,mid,x,mid,delta);
    		add(k*2+1,mid+1,r,mid+1,y,delta);
    	}
    	t[k]=t[k*2]+t[k*2+1];	t[k]%=p;
    }
    
    void cheng(long long k, long long l, long long r, long long x, long long y, long long ch)
    {
    	if(l==x&&r==y){
    		t[k]*=ch;	
    			t[k]%=p;
    		chng[k]*=ch;
    			chng[k]%=p;
    		jia[k]*=ch;
    			jia[k]%=p;
    		return;
    	}
    	pushdown(k,l,r);
    	long long mid=(l+r)>>1;
    	if(x>=mid+1)	cheng(k*2+1,mid+1,r,x,y,ch);
    	else if(y<=mid)	cheng(k*2,l,mid,x,y,ch);
    	else {
    	cheng(k*2,l,mid,x,mid,ch);
    	cheng(k*2+1,mid+1,r,mid+1,y,ch);
    	}
    	t[k]=t[k*2]+t[k*2+1];	t[k]%=p;
    }
    
    long long query(long long k, long long l, long long r, long long x, long long y)
    {
    	if(l==x && r==y){
    		return t[k]%p;
    	}
    	pushdown(k,l,r);
    	long long mid = (l+r)>>1;
    	if(x>=mid+1)	return query(k*2+1,mid+1,r,x,y)%p;
    	else	if(y<=mid)	return query(k*2,l,mid,x,y)%p;
    	else	return (query(k*2,l,mid,x,mid)+query(k*2+1,mid+1,r,mid+1,y))%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++)
    	{
    		int tt;
    		scanf("%d",&tt);
    		if(tt==1){
    			scanf("%d%d%d",&x,&y,&k);
    			cheng(1,1,n,x,y,k);
    		}
    		if(tt==2){
    			scanf("%d%d%d",&x,&y,&k);
    			add(1,1,n,x,y,k);
    		}
    		if(tt==3){
    			scanf("%d%d",&x,&y);
    			printf("%d
    ",query(1,1,n,x,y));
    		}
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    Python的第一个程序
    Spring Boot 之Web开发
    JAVA简介及特性
    Spring Boot与Docker
    ls命令之通配符的使用
    [导入]ASP.NET常用函数
    [导入]再见号称中国最美的女人
    [导入]www.reactos.org一网友介绍的方向为“自己写操作系统的站点”
    [导入] Gmail Chat (Gmail 里的聊天功能)
    [导入]此篇写给城里普通收入的年轻人~~~
  • 原文地址:https://www.cnblogs.com/ZhengkunJia/p/12241093.html
Copyright © 2011-2022 走看看