这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战.
DFS序
DFS序的定义
将树节点按dfs过程中的访问顺序排序,称为dfs序。
性质
一个节点和它的子树在一个连续的区间中。
时间戳
在dfs过程中,设访问一个新节点需要花费1s的时间,我们可以记录下每个节点的进入时间与退出时间,称为时间戳。显然进入时间是dfs序的反函数,即dfs序中的第i个点的进入时间是i。
1 | cpp复制代码int st[M], ed[M]; |
应用
1.判断节点的从属关系
如果st[x]<st[y]并且ed[x]>=ed[y],那么x是y的祖先。
2.POJ 3321 Apple Tree
一棵树,操作一改变一个点的权值,操作二求以某个点为根的子树的权值和。
如果求出了这棵树的dfs序,就可以将问题从树转化为数字序列上的问题。
操作1:单点修改
操作2:区间求和,范围是st[x],ed[x]
使用树状数组维护即可。
1 | cpp复制代码#include <cstdio> |
欧拉序
欧拉序的定义
树在dfs过程中的节点访问顺序称为欧拉序.
欧拉序有很多种,只要整体满足dfs顺序即算欧拉序,应当按实际需要选择适合的欧拉序。
欧拉序与dfs序不同地方在于,欧拉序中每个节点可以出现多次,比如进入一次退出一次,又比如每次回溯时记录一次。
1.每次访问一个节点时记录一次
应用:LCA归约为RMQ
这也是最经典的应用。
LCA(Lowest Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。
RMQ(Range Minimum/Maximum Query),即区间最值查询,是指在数列中求一段区间的最大或最小值。
ST表(Sprase Table),Tarjan发明,可以预处理复杂度O(nlogn),查询复杂度O(1)地解决RMQ问题,不支持修改.
参见:RMQ问题与Sparse Table算法
假设现在需要求x和y的LCA,跑一遍欧拉序并记录时间戳st和ed后,st[x],st[y],ed[x],ed[y]的大小关系只有两种情况(交换后):
- st[x]<st[y]<=ed[y]<=ed[x],表示x是y的祖先。
此时x与y的LCA就是x。 - st[x]<=ed[x]<st[y]<=ed[y],表示x与y没有祖先-后裔关系。
在ed[x]与st[y]之间的节点中深度最小的节点,就是x与y的LCA(不妨纸上画一画)
在实现时,先对欧拉序建立st表,比较规则是选择深度小的,查询只要查询min(deep[x],deep[y]),max(deep[x],deep[y])
之间深度最小的节点即可。
实际上,可以不用额外统计深度,而把比较规则改为选择st[x]小的。(想一想,为什么)
代码
1 | cpp复制代码int st[M], ed[M]; |
Luogu P3379【模板】最近公共祖先(LCA)
模板题
1 | cpp复制代码#include <bits/stdc++.h> |
HDU - 2586 How far away ?
给一棵带边权的无根树,若干次询问,每次询问两个节点的最短距离。
以任意一点为根建立欧拉序,同时dfs出所有节点到根节点的距离,询问a,b的答案就是dis[a]+dis[b]-2*dis[lca(a,b)]
1 | cpp复制代码/* LittleFall : Hello! */ |
终于学到这里了,可以回去补那个该死的cf1051F了QAQ
2. 欧拉序:进入记录一次,退出记录一次
dfs序与欧拉序的花式用法:DFS序详解-比特飞流
这边的方法好像和树链剖分有很大关系,以后学树剖的时候再看看。
BZOJ4034 树上操作
有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个
操作,分为三种:
操作 1 :把某个节点 x 的点权增加 a 。
操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。
操作 3 :询问某个节点 x 到根的路径中所有点的点权和。
这道题,普通做法是树链剖分+数据结构,文艺做法是欧拉序+线段树,玄学做法是欧拉序+树状数组。
好了,因为我不会树链剖分与线段树,使用了玄学做法,参考自(blog.csdn.net/youhavepeop…).
首先求欧拉序,进入记录一次正点权,退出记录一次负点权。
三个操作转化为:
- 两个单点修改
- 区间修改,正的加,负的减
- 区间求和
一个正常的树状数组是做不到第二个操作的,于是用三个树状数组c0,c1,c2维护这个东西。
c0维护原始序列,当有操作1时,c0[st[x]]+=a,c0[ed[x]]−=ac0[st[x]]+=a,c0[ed[x]]-=ac0[st[x]]+=a,c0[ed[x]]−=a
c1记录区间修改,当有操作2时,c1[st[x]]+=a,c1[st[x]]−=ac1[st[x]]+=a,c1[st[x]]-=ac1[st[x]]+=a,c1[st[x]]−=a
c2记录区间修改乘以高度,当有操作2时,c2[st[x]]+=a∗deep[x],c2[ed[x]]−=a∗deep[x]c2[st[x]]+=a*deep[x],c2[ed[x]]-=a*deep[x]c2[st[x]]+=a∗deep[x],c2[ed[x]]−=a∗deep[x]
操作3:ans=sum0(st[x])+sum1(st[x])∗(deep[x]+1)−sum2(st[x])ans = sum_0(st[x])+sum_1(st[x])*(deep[x]+1)-sum_2(st[x])ans=sum0(st[x])+sum1(st[x])∗(deep[x]+1)−sum2(st[x])
多个树状数组的组合,真的很玄学。
1 | cpp复制代码#include <bits/stdc++.h> |
本文转载自: 掘金