线段树基础

Posted adelalove

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树基础相关的知识,希望对你有一定的参考价值。

一,什么是线段树

线段树是一种二叉搜索树,它将一个区间划分成一些单元区间

每个单元区间对应线段树中的一个叶结点

将[1,n]分解成若干特定的子区间(数量不超过4*n)

用线段树对“编号连续”的一些点,进行修改或者统计操作,修改和统计的复杂度都是O(log2(n))

用线段树统计的东西,必须符合区间加法

也就是说,如果已知左右两子树的全部信息,比如要能够推出父节点

否则,不可能通过分成的子区间来得到[L,R]的统计结果

一个问题,只要能化成对一些“连续点”的修改和统计问题,基本就可以用线段树来解决了

解决问题时常会用到离散化

技术图片

 由上图可知,每个节点的左孩子区间范围为[l,mid],右孩子为[mid+1,r]

对于结点k,左孩子结点为2*k(k<<1),右孩子为2*k+1(k<<1|1)

 二,线段树的一些操作

每个人的代码风格不一样,也可以写成结构体,我习惯写数组,结构体的写法和指针的写法网上有,请自行搜索

 1.以线段树求和为例 题目https://www.cnblogs.com/adelalove/p/8683924.html

首先是建树

a、对于二分到的每一个结点,给它的左右端点确定范围。

b、如果是叶子节点,存储要维护的信息。

 c、状态合并。

技术图片
 1 void build(ll o,ll l,ll r)
 2 {
 3     if(l==r)
 4     {
 5         sum[o]=a[l];
 6         return;
 7     }
 8     ll mid=(l+r)>>1;
 9     build(o<<1,l,mid);
10     build(o<<1|1,mid+1,r);
11     sum[o]=sum[o<<1]+sum[o<<1|1];
12 }
View Code

然后查询  

查询某个区间的和

与二分查询法基本一致,如果当前枚举的点左右端点相等,即叶子节点,就是目标节点。

如果不是,因为这是二分法,所以设查询位置为x,当前结点区间范围为了l,r,

中点为mid,则如果x<=mid,则递归它的左孩子,否则递归它的右孩子。

技术图片
 1 ll query(ll o,ll l,ll r,ll x,ll y)
 2 {
 3     if(x<=l && y>=r)
 4     {
 5         down(o,l,r,(l+r)>>1);
 6         return sum[o];
 7     }
 8     ll mid=(l+r)>>1;
 9     down(o,l,r,mid);
10     ll tot=0;
11     if(x<=mid)tot+=query(o<<1,l,mid,x,y);
12     if(y>mid)tot+=query(o<<1|1,mid+1,r,x,y);
13     return tot; 
14 }
View Code

区间修改

给某个区间加上某个值

重点:我们进行这个操作的时候不可能一个点一个点的改(太慢了),那怎么办呢?

我们引入一个新的状态——懒标记。

作用:存储到这个节点的修改信息,暂时不把修改信息传到子节点。你用的时候才给你,不用不给你。

实现思路(重点):

a.增加新的变量,存储这个懒标记。

b.递归到这个节点时,只更新这个节点的状态,并把当前的更改值累积到标记中。

注意是累积,可以这样理解:过年,很多个亲戚都给你压岁钱,但你暂时不用,所以都被你父母扣下了。

c.什么时候才用到这个懒标记?当需要递归这个节点的子节点时,标记下传给子节点。

这里不必管用哪个子节点,两个都传下去。就像你如果还有妹妹,父母给你们零花钱时总不能偏心吧

d.下传操作:

①当前节点的懒标记累积到子节点的懒标记中。

②修改子节点状态。即子节点继承父亲节点的标记

③父节点懒标记清0。这个懒标记已经传给子节点了,父节点的就没必要存在了。

技术图片
 1 void down(ll o,ll l,ll r,ll mid)
 2 {
 3     if(add[o])
 4     {
 5         add[o<<1]+=add[o];
 6         sum[o<<1]+=add[o]*(mid-l+1);
 7         
 8         add[o<<1|1]+=add[o];
 9         sum[o<<1|1]+=add[o]*(r-(mid+1)+1);
10         
11         add[o]=0;
12     }
13 }
标记下放
技术图片
 1 void up(ll o,ll l,ll r,ll x,ll y,ll k)
 2 {
 3     if(x<=l && y>=r)
 4     {
 5         add[o]+=k;
 6         sum[o]+=k*(r-l+1);
 7         return;
 8     }
 9     ll mid=(l+r)>>1;
10     down(o,l,r,mid);
11     if(x<=mid)up(o<<1,l,mid,x,y,k);
12     if(y>=mid+1)up(o<<1|1,mid+1,r,x,y,k);
13     sum[o]=sum[o<<1]+sum[o<<1|1];
14 }
区间加法

 

 

 

 

 

 

以上是关于线段树基础的主要内容,如果未能解决你的问题,请参考以下文章

线段树基础

线段树详解

线段树基础知识----(基础数据结构)--

论线段树

1.1 线段树的基础操作

线段树(SegmentTree)基础模板