16.树与图的遍历
深度优先遍历
深度优先遍历,就是在每个点x上面的的多条分支时,任意选择一条边走下去,执行递归,直到回溯到点x后再走其他的边
1 | int vis[N];//标记每一个点的状态 |
时间戳
按照上述的深度优先遍历的过程,以每一个结点第一次被访问的顺序,依次赋值1~N的整数标记,该标记就被称为时间戳。
标记了每一个结点的访问顺序。
树的DFS序(树链剖分前驱知识)
一般来说,我们在对树的进行深度优先时,对于每个节点,在刚进入递归时和回溯前各记录一次该点的编号,最后会产生一个长度为2N的序列,就成为该树的DFS序。
1 | int a[N],cnt; |
如下图,DFS之后,那么树的每个节点就具有了区间的性质。
dfs序的七个基本问题:
ps:deep[x]为x的深度,l[x]为dfs序中x的位置,r[x]为dfs序中x子树的结束位置
1.点修改,子树和查询
在dfs序中,子树处于一个连续区间中。所以这题可以转化为:点修改,区间查询。用树状数组或线段树即可。
2.树链修改,单点查询
将一条树链x,y上的所有点的权值加v。这个问题可以等价为:
1).x到根节点的链上所有节点权值加v。
2).y到根节点的链上所有节点权值加v。
3).lca(x,y)到根节点的链上所有节点权值和减v。
4).fa(lca(x,y))到根节点的链上所有节点权值和减v。
上面四个操作可以归结为:节点x到根节点链上所有节点的权值加减v。修改节点x权值,当且仅当y是x的祖先节点时,x对y的值有贡献。
所以节点y的权值可以转化为节点y的子树节点贡献和。从贡献和的角度想:这就是点修改,区间和查询问题。
修改树链x,y等价于add(l[x],v),add(l[y],v),add(l[lca(x,y)],-v),add(l[fa(lca(x,y))],-v)。
查询:get_sum(r[x])-get_sum(l[x]-1)
用树状数组或线段树即可。
3.树链修改,子树和查询
树链修改部分同上一问题。下面考虑子树和查询问题:前一问是从贡献的角度想,子树和同理。
对于节点y其到根节点的权值和,考虑其子节点x的贡献:w[x](deep[x]-deep[y]+1) = w[x](deep[x]+1)-w[x]*deep[y]
所以节点y的子树和为:
ps:公式中的v[i]为手误,应为w[i]。
所以用两个树状数组或线段树即可:
第一个维护∑w[i]*(deep[i]+1):支持操作单点修改,区间和查询。(这也就是问题2)
第二个维护∑ w[i]:支持操作单点修改,区间查询。(这其实也是问题2)
4.单点更新,树链和查询
树链和查询与树链修改类似,树链和(x,y)等于下面四个部分和相加:
1).x到根节点的链上所有节点权值加。
2).y到根节点的链上所有节点权值加。
3).lca(x,y)到根节点的链上所有节点权值和的-1倍。
4).fa(lca(x,y))到根节点的链上所有节点权值和的-1倍。
所以问题转化为:查询点x到根节点的链上的所有节点权值和。
修改节点x权值,当且仅当y是x的子孙节点时,x对y的值有贡献。
差分前缀和,y的权值等于dfs中[1,l[y]]的区间和。
单点修改:add(l[x],v),add(r[x]+1,-v);
5.子树修改,单点查询
修改节点x的子树权值,在dfs序上就是区间修改,单点权值查询就是单点查询。
区间修改,单点查询问题:树状数组或线段树即可;
6.子树修改,子树和查询
题目等价与区间修改,区间查询问题。用树状数组或线段树即可。
7.子树修改,树链查询
树链查询同上,等价为根节点到y节点的链上所有节点和问题。
修改节点x的子树权值,当且仅当y是x的子孙节点时(或y等于x),x对y的值有贡献。
x对根节点到y节点的链上所有节点和的贡献为:w[x]*(deep[y]-deep[x]+1)=w[x]deep[y]-w[x](1-deep[x])
同问题三,用两个树状数组或线段树即可。
树的深度
树中各个节点的深度是一种自顶向下的统计信息
起初,我们已知根节点深度是0.若节点x的深度为d[x],则它的子结点 y yy 的深度就是d[y]=d[x]+1
1 | int dep[N]; |
树的重心与size
树的重心是自底向上统计的
树的重心也叫树的质心。对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小。
1 |
|
图的连通块划分
若在一个无向图中的一个子图中任意两个点之间都存在一条路径(可以相互到达),并且这个子图是“极大的”(不能在扩展),则称该子图是原图的一个联通块
如下代码所示,cnt是联通块的个数,v记录的是每一个点属于哪一个联通块
经过连通块划分,可以将森林划分出每一颗树,或者将图划分为各个连通块。
1 | int cnt; |
广度优先搜索
树与图的广度优先遍历,顺便求d数组(树结点的深度/图结点的层次)。
1 | void bfs(){ |
广度优先遍历是一种按照层次顺序访问的方法。
它具有两个重要的性质:
- 在访问完所有的第i层结点后,才会访问第i+1层结点。
- 任意时刻,队列中只会有两个层次的结点,满足“两段性”和“单调性”。
拓扑排序
给定一张有向无环图,若一个序列A满足图中的任意一条边(x,y)x都在y的前面呢么序列A就是图的拓扑排序
求拓扑序的过程非常简单我们只需要不断将入度为0的点加入序列中即可
(入度:有向图中以结点x为终点的有向边的条数)
(出度:有向图中以结点x为起点的有向边的条数)
(无向图中的度:以x为端点的无向边的条数)
1.建立空拓扑序列A
2.预处理出所有入度为deg[i],起初把入度为0的点入队
3.取出队列头节点x,并把x放入队列A中
4.对于从x出发的每条边(x,y),把deg[y]减1,若deg[y] = 0 ,把y加入队列中
5.重复3,4直到队列为空,此时A即为所求
拓扑排序可以判定有向图中是否存在环。若拓扑排序以后的A序列的长度小于图中点的长度,说明有的点没有被遍历,进而说明存在环。
1 | int head[N],ver[N],nex[N],edge[N],tot; |