线段树学习笔记

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


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);//加法
原文地址:https://www.cnblogs.com/sdlang/p/13068227.html