数据结构之线段树
来源:互联网 发布:淘宝上黑曜石是真的吗 编辑:程序博客网 时间:2024/06/08 19:34
一、引例
有M个数排成一列,做N次操作,每次操作包括:
(1)询问指定区间的最大值、最小值
(2)将指定区间的每个数加上一个值
如果按照最朴素的做法,一个个的遍历,时间复杂度:O(MN)。
那么如何解决一个区间求和(最大值,最小值)的问题呢?那么就要用到线段树啦。
二、定义
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
主要用来解决区间查询、区间修改,使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,基本保证每次操作的时间复杂度为O(logN)。
三、实际应用
a.单点更新
1.定义每个结点的信息
线段树是建立在线段的基础上,每个结点都代表了一条线段[a,b]。长度为1的线段称为元线段。非元线段都有两个子结点,左结点代表的线段为[a,(a + b) / 2],右结点代表的线段为[((a + b) / 2)+1,b]。
struct node{ int left,right,sum;//左端点,右端点,和} tree[maxn<<2];
2.更新
void maintain(int root)//更新根节点为左右子结点的和{ int lnode=root<<1; int rnode=root<<1+1; tree[root].sum=tree[lnode].sum+tree[rnode].sum;}
3.递归建树
遇到叶子节点直接赋值,否则递归遍历左右建树,最后回溯即可。
void build(int root,int begin,int end)//树的结点编号 左端点下标 右端点下标{ tree[root].left=begin; tree[root].right=end; if(begin==end)//叶子节点 { scanf("%d",&adj[begin]); tree[root].sum=adj[begin];//单元素 直接赋值 return ; } int mid=(begin+end)>>1; build(root<<1,begin,mid);//更新左子树 build(root<<1+1,mid+1,end);//更新右子树 maintain(root);//存储左右子树的和}
4.单点更新
将一条线段[a,b] 插入到代表线段[l,r]的结点p中,如果p不是元线段,那么令mid=(l+r)/2。如果b<mid,那么将线段[a,b] 也插入到p的左儿子结点中,如果a>mid,那么将线段[a,b] 也插入到p的右儿子结点中。
void update(int root,int pos,int num) //根结点编号 欲修改值的下标 期待的值{ if(tree[root].left==tree[root].right&&tree[root].left==pos) {//若修改的值在这个节点的左右区间之间那么就直接更改此区间的sum值就可 tree[root].sum+=num; return ; } int mid=(tree[root].left+tree[root].right)>>1; if(pos<=mid) update(root<<1,pos,num); else update(root<<1+1,pos,num); maintain(root);//每次都要更新根节点}
5.求和操作
int query(int root,int begin,int end)//求和{ int ans=0; if(begin==tree[root].left&&end==tree[root].right) { return tree[root].sum; } int mid=(tree[root].left+tree[root].right)>>1; if(end<=mid) ans+=query(root<<1,begin,end); else if(begin>=mid+1) ans+=query(root<<1+1,begin,end); else { ans+=query(root<<1,begin,mid); ans+=query(root<<1+1,mid+1,end); } return ans;}
b.区间更新(成段更新)
比如 从[1,10]每个结点的值都+1,普通单点更新就会超时。
*区间更新:
指更新某个区间内的叶子节点的值,因为涉及到的叶子节点不止一个,而叶子节点会影响其相应的非叶父节点,那么回溯需要更新的非叶子节点也会有很多,如果一次性更新完,操作的时间复杂度肯定不是O(lgn),例如当我们要更新区间[0,3]内的叶子节点时,需要更新出了叶子节点3,9外的所有其他节点。为此引入了线段树中的延迟标记概念,这也是线段树的精华所在。
*延迟标记:
因为更新的数很多,所以我每一步的更新不接着算出来,等到最后需要的时候再去取消标记算出来。
比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,它的节点标记为rt,这时tree[rt].l == a && tree[rt].r == b 这时我们可以一步更新此时rt节点的sum[rt]的值,sum[rt] += c * (tree[rt].r - tree[rt].l + 1),注意关键的时刻来了,如果此时按照常规的线段树的update操作,这时候还应该更新rt子节点的sum[]值,而Lazy思想恰恰是暂时不更新rt子节点的sum[]值,到此就return,直到下次需要用到rt子节点的值的时候才去更新,这样避免许多可能无用的操作,从而节省时间 。
用lazy标记,等到当前区间比我需要的目标区间大的时候,我必须用到下面的值了,必须往下修改了,这时候,我们就把之前堆积起来的懒惰标记pushdown了,于是就有了一个神奇的pushdown操作。
其他的建树什么的和单点更新一样,只是多了lazy标记和pushdown。
void pushdown(LL root) //向下传递lazy标记 { if (tree[root].lazy) { tree[root<<1].lazy+=tree[root].lazy; tree[root<<1+1].lazy+=tree[root].lazy; tree[root<<1].val+=tree[root<<1].len*tree[root].lazy; tree[root<<1+1].val+=tree[root<<1+1].len*tree[root].lazy; tree[root].lazy=0; }}
c.区间合并
不过还没做过这方面的题QAQ
参考博客
很有趣很好懂
很官方很全面
来做题吧 超全
ps:之前看过,今天再看像重新学了一遍(>_<)
今天距离省赛过去已经一个星期了,该调整回来了。
还是不够强,继续修炼吧QAQ。
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构之线段树
- 数据结构 之 线段树
- 数据结构之线段树
- 数据结构之线段树
- vue2.0中.vue文件的post请求
- (27)类别查看器
- 打开控制台的方法
- USB驱动架构浅析
- Linux
- 数据结构之线段树
- matlab2016并行操作测试
- node入门(二)-- 模块系统/cheerio/webSocket
- 吴军《数学之美》部分概念笔记(1-11章)
- 状态压缩DP(入门)
- Linux命令基础21-第一个bash脚本
- 类的加载机制2
- /wd4430
- 输入子系统(四)