• 线段树学习笔记


    很久之前写的学习笔记,就搬过来了


    Q:线段树是什么?
    A:一种数据结构,支持(O(log(N)))修改和查询区间,所以在(N)的序列(M)次查询下,复杂度只有(O(Mlog(N))),相比起朴素算法的(O(N))查询和修改,优秀的很多。

    那么怎么实现呢?

    我们不妨考虑一种下面这样的结构

    线段树

    怎么样,是不是很像一棵完全二叉树,这样子不难看出复杂度是(O(log(N)))级别的,但是要注意的一点是,线段树的数组一定要开到(4N)

    建树

    对于这个题而言,我们用(su[4N])数组来维护区间的和,那么此线段树可以通过递归的到,即(build(k*2,l,mid))取得左儿子,(build(k*2+1,mid+1,r))取得右儿子,当(l=r)(su)便等于当前值(a[l])代码如下

    ll build(ll k,ll l,ll r)
    {
    	if (l==r)
    	{
    		su[k]=a[l];
    		return 0;
    	}
    	ll mid=(l+r)>>1;
    	build(k*2,l,mid);
    	build(k*2+1,mid+1,r);
    	su[k]=su[k*2]+su[k*2+1];
    }
    

    区间加

    如果说直接将v加到su里,那么肯定会tle,所以我们考虑用一个标记,即Lazy Tag,当要对区间([x,y])进行加法,那么我们给这段区间打上标记,(add[k]=v),在之后的询问和修改中再把标记进行下放,这样子的复杂的仍然是(O(log(N)))代码如下

    ll add(ll k,ll l,ll r,ll v)  //打标记
    {
    	ad[k]+=v;
    	su[k]+=(r-l+1)*v;
    }
    ll pd(ll k,ll l,ll r,ll mid)  //下放标记
    {
    	if (!ad[k])return 0;
    	add(k*2,l,mid,ad[k]);
    	add(k*2+1,mid+1,r,ad[k]);
    	ad[k]=0;
    }
    ll change(ll k,ll l,ll r,ll x,ll y,ll v) //区间加
    {
    	if (l>=x&&r<=y)return add(k,l,r,v);
    	ll mid=(l+r)>>1;
    	pd(k,l,r,mid);
    	if (x<=mid)change(k*2,l,mid,x,y,v);
    	if (mid<y)change(k*2+1,mid+1,r,x,y,v);
    	su[k]=su[k*2]+su[k*2+1];
    }
    

    区间询问和

    这个就变得非常简单了,只要分别递归有关x和y的区间,加起来即可。注意在询问时也要下放标记。 代码如下

    ll que(ll k,ll l,ll r,ll x,ll y)
    {
    	if (l>=x&&r<=y)return su[k];
    	ll mid=(l+r)>>1,res=0;
    	pd(k,l,r,mid);
    	if (x<=mid)res+=que(k*2,l,mid,x,y);
    	if (mid<y)res+=que(k*2+1,mid+1,r,x,y);
    	return res;
    }
    

    区间乘

    既然多了乘法,我们肯定就要再开一个(LazyTag:mu[4*N])开始时一定要初始化为1!,而和加法的(LazyTag)不同的是在改变乘法(LazyTag)时,也要改变加法的(LazyTag),即

    ll mul(ll k,ll l,ll r,ll v)
    {
    	mu[k]=(mu[k]*v)%p;
    	ad[k]=(ad[k]*v)%p;
    	su[k]=(su[k]*v)%p;
    }
    

    然后我们再考虑(pushdown)操作,假如一个序列([1,3,5,9,12]),先在([2,4])区间加上一个数(k),序列变为([1,3+k,5+k,9+k,12]),然后在([3,5])区间乘上一个数(r),序列变为([1,3+k,(5+k)*r,(9+k)*r,12*r]),
    由此看出,我们如果先(pushdown)加法,再(pushdown)乘法,会导致之前的加法也被乘一次,也就是((a+b)*c)(b)并不需要(*c),也就是我们想要(a*c+b),所以就要先(pushdown)乘法,再(pushdown)加法,即

    pudn(k,l,r,mid);//乘法
    pd(k,l,r,mid);//加法
    
  • 相关阅读:
    Java设计模式-状态模式(State)
    Java设计模式-备忘录模式(Memento)
    Java设计模式-命令模式(Command)
    Java设计模式-责任链模式(Chain of Responsibility)
    Java设计模式-迭代子模式(Iterator)
    Java设计模式-观察者模式(Observer)
    Java设计模式-模板方法模式(Template Method)
    Java设计模式-策略模式(strategy)
    Java设计模式-享元模式(Flyweight)
    Java设计模式-组合模式(Composite)
  • 原文地址:https://www.cnblogs.com/sdlang/p/13068227.html
Copyright © 2020-2023  润新知