数据结构之线段树入门

一、前言

对于维护区间连续和问题,我们已经学了很多种算法和数据结构,在规定n<=100000,m(操作数)<=200000,内,暴力算法可以解决单点修改,单点求值。前缀和算法可以解决区间求和问题,而最近学的树状数组可以解决单点修改,区间求和的问题。而当我们需要区间修改时,上边的三种算法都将失效,我们亟待引入一个新的数据结构——线段树。

二、线段树基本思想

1.线段树是一棵二叉树,每一个子节点存取的是一段区间所需要维护的信息,如最大值,最小值,或区间和。

2.线段树基本思想:分治

3.线段树每个节点都以结构体的方式存储,这个结构体有4个属性:左端点、右端点、所维护的信息以及LazyTag(后面会详细说明)

下图很好地阐释了线段树储存信息的方式:

当我们需要求1-3区间和时,只需要调取1-2段和3段的信息即可,具体如何调取将会在以下说明。

注:以下维护信息均为区间和。

三、线段树五大基本操作之一 —— 建树

线段树的建树过程实际上是自底向上计算初始值的过程,从顶向下递归,如果是叶子节点那么就输入值,输入完毕后回溯时计算父节点的权值。

线段树建树代码如下:(注意:一定要把结构体开到4*n级别,手画一棵线段树就知道了!

void build(int l,int r,int k)
{
	tree[k].l=l;tree[k].r=r;
	if(l==r)
	{
		scanf("%lld",&tree[k].w);
		return;
	}
	int mid=(l+r)/2;
	build(l,mid,k*2);
	build(mid+1,r,k*2+1);
	tree[k].w=tree[k*2].w+tree[k*2+1].w;
}

四、线段树五大基本操作之二 —— 单点查询

由于线段树一个非叶节点的两棵子树储存的是这个区间的一半,我们可以根据这个特点每次对范围进行一半的缩小,直到递归到叶子节点为止。时间复杂度为log(n)

线段树单点查询代码如下:

int query_point(int k)
{
	int l=tree[k].l,r=tree[k].r;
	if(l==r)return tree[k].w;
	int mid=(l+r)/2;
	if(x<=mid)return query(k*2);
	return query(k*2+1);
}

  

五、线段树五大基本操作之三 —— 单点修改

单点修改和单点查询原理一样,只需在回溯时维护一下信息即可。

线段树单点修改代码如下:

void add_point(int k,int w)
{
	int ll=tree[k].l,rr=tree[k].r;
	if(ll==rr)
	{
		tree[k].w+=w;
		return;
	}
	int mid=(ll+rr)/2;
	if(x<=mid)add(k*2,w);
	else add(k*2+1,w);
	tree[k].w=tree[k*2+1].w+tree[k*2].w;
	return;
}

  

六、线段树五大基本操作之四 —— 区间求和

区间求和依旧是根据线段树的特点,尽可能调用深度较浅的节点,当目前区间被所需区间完全覆盖时,就加上,否则继续递归。

线段树区间求和代码如下:

int query_interval(int k)
{
	int l=tree[k].l,r=tree[k].r;
	if(l>=x&&r<=y)
	{
		ans+=tree[k].w;
		return;
	}
	int mid=(l+r)/2;
	if(x<=mid)query(k*2);
	if(y>mid)query(k*2+1);
}

  

六、线段树之LazyTag

使用线段树的一个重要目的就是进行区间修改,而如果按照单点修改的思路,修改整个区间时将会修改整棵线段树,比朴素算法还劣,这时我们就需要引入LazyTag(懒标记),顾名思义,懒标记十分的懒,只在需要的时候下放,否则就一直呆着。为什么不能一下就全都修改呢?是因为我们其实有很多不需要的信息被下放了,因此造成TLE。因此采用LazyTag改动尽量少点的权值,并改动LazyTag的值,在进行递归的时候为了保证正确性需要先改动子节点的值,该操作叫做标记下放(pushdown),标记下放的时候直接下放到k*2和k*2+1中,并累积到权值和当前节点的LazyTag里(一定要累积,在该节点下放之前父节点可能不止一次下放)注意:在使用LazyTag的程序中,五种基本操作在递归前都需要pushdown!!!

pushdown代码如下:

void pushdown(int k)
{
	tree[k*2].lazytag+=tree[k].lazytag;
	tree[k*2].w+=(tree[k*2].r-tree[k*2].l+1)*tree[k].lazytag;
	tree[k*2+1].lazytag+=tree[k].lazytag;
	tree[k*2+1].w+=(tree[k*2+1].r-tree[k*2+1].l+1)*tree[k].lazytag;
	tree[k].lazytag=0; 
	return;
}

  

七、线段树五大基本操作之五 —— 区间修改

有了LazyTag,区间修改代码不难写出,和区间求和代码思路一样,需要的时候标记下放即可

线段树区间修改代码如下:

void add(int k,int w)
{
    int ll=tree[k].l,rr=tree[k].r;
    if(ll>=x&&rr<=y)
    {
        tree[k].w+=(rr-ll+1)*w;
        tree[k].lazytag+=w;
        return;
    }
    if(tree[k].lazytag)pushdown(k);
    int mid=(ll+rr)/2;
    if(x<=mid)add(k*2,w);
    if(y>mid)add(k*2+1,w);
    tree[k].w=tree[k*2].w+tree[k*2+1].w;
    return;
}

 

线段树五种基本操作及LazyTag非常重要,请大家一定要好好理解!!

如果你喜欢我的博客,别忘了点个赞哦~~~

猜你喜欢

转载自www.cnblogs.com/szmssf/p/11042137.html