线段树学习总结

线段树是一种非常重要的数据结构,主要用动态维护区间信息。

线段树是的功能是把一个区间划分成许多个小区间,每个长度大于1的区间被从中间分成两段,分别对应该节点的左子树和右子

树,如图:


所以可以看出,假设一个节点对应的区间为 [l,r], 记mid=(l+r)/2,则其左子树对应区间为 [l,mid],右子树对应区间为[mid+1,r]。

复杂度方面,线段树所需要的数组大小是数列长度的4倍,时间复杂度为O(NlogN)。

线段树的基本操作

1.建树

建树的过程就是从根节点开始,递归建立左右子树。如果是叶子节点,赋值并回溯即可。注意minv数组初始值应为一个极大的数

int maxv[maxn*4],minv[maxn*4],sum[maxn*4];
int a[maxn];//原始数列 
void build(int root,int l,int r)
{
	if(l==r) maxv[root]=minv[root]=sum[root]=a[l];//叶子节点
	else{
		int mid=(l+r)/2;
		build(root*2,l,mid);
		build(root*2+1,mid+1,r);
		maxv[root]=max(maxv[root*2],maxv[root*2+1]);
		minv[root]=min(minv[root*2],minv[root*2+1]);
		sum[root]=sum[root*2]+sum[root*2+1];
	} 
}

2.区间查询

从根节点开始递归,如果查询区间完全包含在当前节点的区间中,直接返回当前节点的值即可。如果查询区间和左子树有交集,递归左子树,和右子树有交集就递归右子树。

代码(以求最大值为例)

int query(int root,int ql,int qr,int l,int r)
{
	int ans=0;
	if(ql<=l&&qr>=r) return maxv[root];
	int mid=(l+r)/2;
	if(ql<=mid) ans=max(ans,query(root*2,ql,qr,l,mid));
	if(qr>mid) ans=max(ans,query(root*2+1,ql,qr,mid+1,r));
	return ans;
}

注意求最小值时ans的初值

3.单点修改

从根节点开始递归,判断修改的位置在当前节点的左子树还是右子树,递归相应的子树,然后更新当前节点的值。

void update(int root,int l,int r,int pos,int val)
{
	if(l==r){
		maxv[root]=val;
		return;
	} 
	int mid=(l+r)/2;
	if(pos<=mid) update(root*2,l,mid,pos,val);
	else update(root*2+1,mid+1,r,pos,val);
	maxv[root]=max(maxv[root*2],maxv[root*2+1]);
}

4.区间修改

线段树最重要的功能。我们不可能对区间中的每个数做单点修改,时间复杂度将退化为N^2logN,无法接受。

所以这里引入线段树的核心操作,lazytag。

对于每一个节点,我们加一个变量,记录该点进行的修改操作,称为懒标记,父节点的修改操作会对子节点产生影响。

每次递归访问子树之前,都要进行一次维护,将该节点的lazytag值传给子节点,并相应地修改子节点的值,最后清空该节点的lazytag。

代码(以加法为例,维护区间和):

struct node
{
	int sum;
	int tag;
}tr[maxn*4];
void pushdown(int root,int l,int r)
{
	if(tr[root].tag){
		int mid=(l+r)/2;
		tr[root*2].tag+=tr[root].tag;
		tr[root*2+1].tag+=tr[root].tag;
		tr[root*2].sum+=tr[root].tag*(mid-l+1);
		tr[root*2+1].sum+=tr[root].tag*(r-mid);
		tr[root].tag=0;
	}
}

所以区间修改的代码就很简单了,从根节点开始递归,如果当前节点完全包含在修改区间内,修改当前节点的lazytag和值然后回溯即可,否则先执行一次pushdown操作,再判断是否包含左子树,右子树,相应递归即可。

void update(int root,int ul,int ur,int l,int r,add)
{
	if(ul<=l&&ur>=r){
		tr[root].tag+=add;
		tr[root].val+=add*(r-l+1);
		return;
	}
	pushdown(root,l,r);
	int mid=(l+r)/2;
	if(ul<=mid) update(root*2,ul,ur,l,mid,add);
	if(ur>mid) update(root*2+1,ul,ur,mid+1,r,add);
	tr[root].val=tr[root*2].val+tr[root*2+1].val;
}

例题:

Luogu3372,3373(Luogu2023,BZOJ1798) 区间修改,区间查询

Luogu1198(BZOJ1012),Luogu1531 单点修改,区间最值

Luogu1816 RMQ问题(可ST表)

猜你喜欢

转载自blog.csdn.net/qq_42318710/article/details/80584320