首页 > 其他分享 >线段树(Segment Tree)是一个基于分治的数据结构。

线段树(Segment Tree)是一个基于分治的数据结构。

时间:2022-11-07 20:57:26浏览次数:58  
标签:return int tr Tree tree tl 区间 数据结构 Segment

线段树(Segment Tree)是一个基于分治的数据结构。

线段树杂谈

 

概念:

线段树(Segment Tree)是一个基于分治的数据结构。

通常处理区间序列中的查询更改问题。大体上有单修,单查,区修,区查等操作。但因为其可维护变量的多样性,所以常在各类题目中遇到。准确说,是各类优化中遇到。

线段树是个有根二叉树,我们记为 t,其每个节点 t[p] 均保存着所应该记录的关键信息(依题目情况而定)。对于每一个区间,我一般不喜欢在结构体里记录它的区间端点信息,一般在函数递归中开两个参数进行处理

实现1(单点修改,区间查询):

首先我们有这样一个序列a:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

我们希望对其中下标为 i 的元素进行修改,并且能够快速查找区间 l 到 r 中的最大值。

Step 1,建树:
struct node{
	int mx;
}tree[MAXN];//记录每个节点里的关键信息,这里记录了节点区间里的最大值
void build(int i,int tl,int tr){//递归建树,i表示节点编号,tl,tr表示该节点所含的区间(后同)
	if(tl == tr){
		tree[i].mx = a[tl];
		return;
	}
	int mid = (tl + tr) / 2;
	build(i << 1,tl,mid);//进行递归建树操作,分别建左子树和右子树
	build((i << 1) + 1,mid + 1,tr);
	tree[i].mx = max(tree[i<<1].mx,tree[(i<<1) + 1].mx);//子树建完后,进行上传操作,更新该区间tree[i]的最大值(意会一下)
}
Step 2,单点修改:
void change(int i,int tl,int tr,int pos,int num){//pos为要修改的下标,num为修改后的值
	if(tl == tr){//已到达要修改的下标
		tree[i].mx = num;
		return;
	}
	int mid = (tl + tr) / 2;
	if(pos <= mid){//如果包含在左子树里,那么就在该子树里进行递归直到到达目标节点
		change(i<<1,tl,mid,pos,num);
	}
	else{//被包含在右子树里
		change((i<<1) + 1,mid + 1,tr,pos,num);
	}
	tree[i].mx = max(tree[i<<1].mx,tree[(i<<1) + 1].mx);//修改完值后,自然要更新区间包含这个下标的树上的节点
}
Step 3,区间查询:

查询 l 到 r 这一区间里的最大值。

int query(int i,int tl,int tr,int l,int r){
	if(l > r)return -MAXN;//区间越界,返回最小值
	if(tl == l && tr == r){
		return tree[i].mx;
	}
	int mid = (tl + tr) / 2;//如果查询的区间被左右子树各包含一点,那么将这个查询的区间分为落在左子树里的区间和落在右子树里的区间,对分后的两个区间所返回的最大值取一次最大值
	return max(query(i<<1,tl,mid,l,min(r,mid)),query((i<<1) + 1,mid + 1,tr,max(l,mid + 1),r));
}

单点修改,区间查询的方法就介绍到这里了。

实现2(区间修改,单点/区间查询):

如果说单点修改,区间查询的实现中最重要的就是数据的上传操作,那么我觉得区间修改,单点/区间查询最重要的就是数据的下传操作。

什么是下传操作?我们通过对下面这道题的分析来进行理解。

有一列长度为 n 的路灯,每一次操作下标 l 到 r 的路灯的开关,使得每个路灯的状态发生改变(亮变灭 or 灭变亮)。所有灯初始时都是灭的,经过 m 次操作后,有 x 次查询,每一次查询操作给出 l r 两个下标,查询 l 到 r 中有多少盏路灯是亮着的。

在这道题目中,对于修改操作中的 l r,如果我们将这个区间里面每个路灯的状态逐一进行改变,时间复杂度肯定是不会令人满意的。但是我们可以对每个节点设置一个标记 sta,1 表示该节点所表示的区间内的所有灯都是亮着的,0 表示所有灯都是灭的,−1 表示该区间里既有灭的,又有亮的。

在这个题里,因为所有灯初始为灭的,且 sta 的默认值为 0,我们就可以取消建树操作。

Step 1,区间修改:
#define ll i<<1
#define rr (i<<1) + 1
void pushdown(int i, int tl, int tr) {//进行数据下传操作
    int mid = (tl + tr) / 2;
    if (tree[i].sta != 0) {
        tree[ll].sta = !tree[ll].sta;
        tree[rr].sta = !tree[rr].sta;
        tree[ll].cnt = (mid - tl + 1) - tree[ll].cnt;
        tree[rr].cnt = (tr - mid) - tree[rr].cnt;
        tree[i].sta = 0;
    }
}
void change(int i, int tl, int tr, int l, int r) {//进行区间修改
    if (l > tr || r < tl)
        return;
    if (l <= tl && r >= tr) {
        tree[i].sta = !tree[i].sta;
        tree[i].cnt = tr - tl + 1 - tree[i].cnt;//若该区间被完全包含,那么直接将他进行修改,不再递归
        return;
    }
    pushdown(i, tl, tr);//如果没有被完全包含,那么就需要继续递归处理,此时进行数据下传操作
    int mid = (tl + tr) / 2;
    change(ll, tl, mid, l, r);//左子树
    change(rr, mid + 1, tr, l, r);//右子树
    tree[i].cnt = tree[ll].cnt + tree[rr].cnt;//将儿子节点的信息返回到父亲节点,即数据上传操作
}
Step 2,区间查询:
int count(int i, int tl, int tr, int l, int r) {
    if (tl > r || tr < l)//超出界限,无答案
        return 0;
    if (l <= tl && r >= tr)//若该区间被完全包含,直接返回整个区间的信息
        return tree[i].cnt;
    int ans = 0;
    pushdown(i, tl, tr);//子树的信息可能未被更新,因此下传数据
    int mid = (tl + tr) / 2;
    ans += count(ll, tl, mid, l, r);
    ans += count(rr, mid + 1, tr, l, r);//ans分别加上左子树和右子树的答案
    return ans;
}
PERL 复制 全屏

通过这两个题,我们能够更加深入地理解线段树,搞明白其中最重要的父节点数据下传,子节点数据上传等操作,这样有利于我们更清晰地进行线段树的处理。

最后,在面对线段树的题时,最重要的是分析到底该维护哪些信息,并如何进行转移。线段树其实从根本上说是一种思想,因此,我们无须拘泥于所谓的模板,根据题目灵活应对即可。

  分类: 线段树

标签:return,int,tr,Tree,tree,tl,区间,数据结构,Segment
From: https://www.cnblogs.com/Leo_wl/p/16867401.html

相关文章

  • 数据结构设计
    1.LRU(LeastRecentlyUsed)2.LFU(LeastFrequentlyUsed)4.求中位数......
  • 【数据结构-链表】链表的相关算法
    目录1删除元素1.1删除值为x的所有结点1.1.1不带头结点的单链表1.1.2带头结点的单链表1.2删除重复元素1.2.1不带头结点的单链有序表1.2.2带头结点的单链有序表2链......
  • Graphics Stack总结(三) Mesa source tree概览
     回顾上篇文章中我们介绍了Mesa的loder模块,该模块负责自动为我们的硬件选择正确的driver。如果loader没能为找到匹配的hardwaredriver,那么它会fallback到softwaredriv......
  • 解决GIT可视化工具Sourcetree的远程仓库无法clone的问题
    最简单的方法就是先用gitbash拉取仓库的一个项目,完成sourcetree和本地的链接,然后将这个项目用sourcetree添加上,这样就自动完成了链接。接着直接用clone远程仓库代码就成功......
  • 『数据结构与算法』解读递归算法!
    文章目录​​一.什么是递归​​​​1.1.递归的定义​​​​1.2.何时使用递归​​​​1.3.递归模型​​​​二.递归算法的设计​​​​2.1.递归算法设计的步骤​​​​......
  • 数据结构 玩转数据结构 6-7 二分搜索树的中序遍历和后续遍历
    0课程地址https://coding.imooc.com/lesson/207.html#mid=13460 1重点关注1.1什么是中序遍历和后续遍历中序遍历:就是先遍历左节点,再遍历根......
  • 3、SourceTree通过PUTTY连接GitLab
    一、生成公钥和私钥使用命令行生成(两种生成方式选择一种即可) 1、安装SourceTree打开SourceTree,点击“命令行模式”。2、输入如下命令生成key“[email protected]”是你在......
  • TAVLTree及其相关应用
    1.TAVLTreeTAVLTree maintainsabalancedAVLtree.Thetreeconsistsof TAVLTreeNode nodes,eachofwhichhasa Data pointerassociatedwithit.The TAVL......
  • 查找——数据结构与算法学习
    查找算法二分查找(初始二分查找)算法原理:就是一个分治的思想:分而治之,不断划分数据的查找范围,就可以提高查找效率,效率达到了O(logn)前提:必须对应的是有序列表//手写二分......
  • 【模板】动态树 Link-Cut Tree
    postedon2022-08-1718:05:59|under模板|sourcetemplate<intN>structlctree{ intval[N+10],sum[N+10],fa[N+10],ch[N+10][2],rev[N+10]; boolgetson(intp)......