学不会的线段树

前言(胡言乱语)

“杭电杯”被狠狠薄纱???,发现队友都是大佬,只有我是蒟蒻!!!具体表现为(包括但不限于)只有我还不会线段树?,狠狠泪目!这就学?(・_・;

线段树的概念

线段树(Segment Tree)是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。

线段树的应用

线段树适合解决的问题的特征是:大区间的解可以从小区间的解合并而来(区间加法!),即对于一个[L,R] = [L,M] + [M + 1,R].线段树对于区间的修改、维护和查询的时间复杂度优化为log级别。如果是要修改一个元素(单点修改),直接修改叶子节点上元素的值,然后从从下往上更新线段树;如果修改的是一个区间的元素(区间修改),需要用到Lazy-Tag技术。

线段树的构造

节点的构造

我们一般采用静态数组实现满二叉树,父节点和子节点之间的访问非常简单,缺点是最后一行有大量节点被浪费。

//定义二叉树数据结构struct {    int L;//左儿子    int R;//右儿子    int data;//记录线段i的最值或区间和}tree[N * 4]; //直接用数组表示二叉树int tree[N * 4];//记录线段i的最值或区间和

这两种都满足index_l = index_p << 1,index_r = index_p << 1 | 1

注意:二叉树的空间需要开N * 4,即元素数量的4倍

建树

建树的过程就是利用递归的思想,从上往下建树,从下往上传递区间值

int ls(int p){    return p << 1;} int rs(int p){    return p << 1 | 1;} void push_up(int p){    tree[p] = tree[ls(p)] + tree[rs(p)];} void build(int p,int pl,int pr){    if(pl == pr){        tree[p] = a[pl];//给叶节点赋值        return ;    }    int mid = (pl + pr) >> 1;//折半    build(ls(p),pl,mid);//构造左子树    build(rs(p),mid + 1,pl);//构造右子树    push_up(p);//更新节点信息}

区间查询

查询区间最值

以上面这个图为例,我们现在查询区间[3,7]之间的最值,从根节点开始,查询左右子树,存在一下3种情况
1、如果要查询的区间完全覆盖查询区间,就可以直接取值
2、如果要查询的区间与查询区间有交集,继续向下查询
3、如果要查询的区间与查询区间没有交集,那这个节点以及所有子节点的值对结果没有贡献

查询区间和

以上面这个图为例,我们现在查询区间[3,7]之间的和,从根节点开始,查询左右子树,存在一下3种情况
1、如果要查询的区间完全覆盖查询区间,就可以直接取值
2、如果要查询的区间与查询区间有交集,继续向下查询
3、如果要查询的区间与查询区间没有交集,那这个节点以及所有子节点的值对结果没有贡献

不难发现,无论要查询的是什么基本的思路都是:根据区间直接的覆盖情况来决定取值还是继续递归。

区间查询代码

int query(int l,int r,int p,int pl,int pr){//区间查询    if(l <= pl && pr <= r){//完全覆盖        return tree[p];//根据需要返回相应的值    }    int mid = (pl + pr) >> 1;    if(l <= mid){//左子树上有交集        res += query(l,r,pl, ls(p),mid);    }    if(r > mid){//右子树上有交集        res += query(l,r,pr,mid + 1,rs(p));    }    return res;}

区间操作与Lazy-Tag

Lazy-Tag

利用线段树的特征:线段树的节点tree[i]记录了区间i的值。可以再定义一个tag[i]用它统一记录区间i的修改,而不是一个一个地修改区间内的每个元素。看起来就慢的嘞
懒标记的精髓是:打标记下传操作。若修改的是一个线段区间,就只对这个区间进行整体上的修改,打上tag标记,而它的子树先不进行修改,等需要使用到它的子树的时候,再对它的子树进行修改,把标记下传。

区间修改

图片[1]-学不会的线段树-五八三
以上图为例,我们现在要把区间[4,7]内的每个元素加2,按以下步骤进行操作:
1、左子树递归到节点11(标红的点),[4,4]被完全覆盖,打上标记tag[11] = 2,更新节点的值,不再继续深入
2、左子树递归返回,更新父节点的值

3、右子树递归到节点6,[5,6]被完全覆盖,打上标记tag[6] = 2,更新节点的值,不再继续深入
4、右子树递归返回,更新父节点的值

5、右子树递归到节点14,[7,7]被完全覆盖,打上标记tag[14] = 2,更新节点的值,不再继续深入
6、右子树递归返回,更新父节点的值

Q:好像tag并没有什么用啊
A:因为我们这里只对某个区间进行了一次操作,如果对多个区间进行多次操作的话,tag的作用就能很好的体现出来了

区间修改代码

void addTag(int t,int p,int pl,int pr){    tag[p] += t;//给节点p打标记    tree[p] += t * (pr - pl + 1);//更新节点信息} void push_down(int p, int pl, int pr){    if(tag[p]){//如果有标记,把标记下传        int mid = (pl + pr) >> 1;        addTag(tag[p],ls(p),pl,mid);        addTag(tag[p],rs(p),mid + 1,pr);        tag[p] = 0;//自己的标记被传走    }} void update(int l,int r,int p,int pl,int pr,int t){    if(l <= pl && pr <= r){//完全覆盖        addTag(t,p,pl,pr);//给节点p打标记        return ;    }    push_down(p,pl,pr);    int mid = (pl + pr) >> 1;    update(l,r,ls(p),pl,mid,t);    update(l,r,rs(p),mid + 1,pr,t);    push_up(p);//更新节点信息}

后记

如果有任何问题,欢迎随时指正,感激不尽???
ps:换了电脑?都没之前的生动了/(ㄒoㄒ)/~~

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYnFC4Wb' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片