2023.7.20 学习笔记

拓扑 & Tarjan

拓扑排序

运用条件:

  • 1.有向无环图
  • 2.需要求拓扑序

例子:给定一张有向图, 构造一个满足以下条件的节点序列:对于图每条有向边 (u, v) ,序列中 v 排在 u 之后。

Solution:

d[x] 为点 x 的入度, 用队列维护入度为0的点, 每次从队列中取出一个点, 并把该点连边指向的所有点入度-1, 若连向的点入度为0则入队, 所有点的出队顺序即为一组合法序列。


对于一个图,如何判断其拓扑序是否唯一?

在跑拓扑排序的时候判断队列大小是否始终为1,若队列大小不为1,则此时拥有多种选择,故拓扑序不唯一。

来看下一个问题:如何求出字典序最小的拓扑序列?

讲队列改为优先队列,在有多个可选节点时将最小的点入队。


eg1. 给定一张 DAG (有向无环图),边有边权, 求一条边权和最大的路径?

Solution:

dp[i] 为以 i 为结尾的最长路长度, dp[i] = \max(dp[前驱] + w_k, dp[i])

while(非空)
{
	取出队首;
	for(遍历从u出发的边)
	{
		dp[v] = max(dp[v], dp[u] + w[i]);
		in[v]--;
		if(in[v] == 0) 入队
	}
}


eg3.给定一张 DAG (有向无环图), 求图中的路径条数?

根据加法原理: dp[i] 表示到 i 的路径条数, dp[v] += $dp[u]$。


eg4. 航空管制

经过思考,贪心假了,可能导致后续航班无法符合要求,因为拓扑排序时无法预测之后的航班安排是否有调整空间。

Solution :倒着做!反向建图,从后向前计算拓扑序列,第一问从后向前安排, 每次取一个符合要求的航班起飞,第二问对除当前飞机以外的部分跑拓扑,直到队列为空则此时的位置即为当前飞机的答案。


Tarjan

前置知识: 连通分量, 强连通分量, 树上边。

连通分量:无向图中的连通块, 即对于连通分量中的任意两个点$i, j$,
ijji 都存在至少一条路径。

强连通分量: 有向图中的 “双向可达连通块”。
即对于强连通分量中的任意两个点$i, j$, ijj 到 $i$都存在至少一套路径。

树上边:

  • 树边:搜索书上的边
  • 前向边:从祖先指向后代的边
  • 返祖边:从后代指向祖先的边
  • 横向边:上述以外的边

一个结论或者说性质:$dfs$搜索一张无向图, 搜索树上一定不含

正题:

介绍:一般可以用tarjan算法求有向图的强连通分量( SCC )及其相关信息。从任意一点出发搜索遍历一张图(的一部分), 每个节点仅防问一次, 只保留所有搜索递归回溯的边, 可以得到一颗生成树。

若要使$x$ 可以指向 $y$, 则满足以下条件之一

  • 1.从 x 出发有一条指向 y 的返祖边
  • 2.从 x 出发, 有一条横叉边指向 z ,而 z 有到达 y 的路径。

而Tarjan的做法是对每个未访问过的节点,遍历其搜索树,同时用栈存储访问到的点。
定义:

  • dfn[u] 为节点 udfs
  • low[u]uu 的子树节点,只经过最多一条返祖边, 能够追溯到的最早的 dfn.

low[u] = \min{

  • dfn[u],
  • low[v], vu 的子树里
  • dfn[v], (u, v) 为返祖边 (或横向边)

}

结论:当 dfn[u] == low[u] 时, 以 u 为根的未出栈子树节点是一个强连通分量,将他们依次弹出并记录即可。

代码实现

for(int i = 1;i <= n;i++)
   	if(!dfn[i])
      	tarjan(i);

void tarjan(int x){
	dfn[x] = low[x] = ++top;
    vis[x] = 1;
    stk.push(x)
    for(int i = head[x];i != -1;i = a[i],next)
    {
     	if(!dfn[a[i].to]){
        	tarjan(a[i].to);
          	low[x] = min(low[x], low[a[i].to]);
        }
      	else if(vis[a[i].to])
          	low[x] = min(dfn[a[i].to, low[x]);
    }
    if(dfn[x] == low[x]){
    	cnt++;//cnt表示SCC数量
      	while(stk.top() != x)
        {
         	vis[q.top()] = 0;
          	b[stk.top()] = cnt;
          	stk.pop();
        } 
      	vis[x] = 0;
      	b[x] = cnt;
     	stk.pop();
}

求出强连通分量能干啥?

缩点:将每个强连通分量缩成一个点, 新图中两个点之间有边 当且仅当 原图中对应的两强连通分量之间有边。就可以把有向无环图变为无向图, 而 $DAG$(有向无环图)具有许多优良的性质。

Solution:

显然一个强连通分量中只需要收买最便宜的点, tarjan缩点, 点权设为原图中点的最小值 ,然后计算新图中所有入度为0的点是否都能被收买, 将其花费求和即可。

eg7. 最大半连通子图

任意一个强连通分量中的点两两双向可达, 故一个强连通分量都在同一个半连通子图中或都不在。

割点

对于无向图中一点 x ,若删掉 x 和与 x 相连的边后新图不连通 则称 x 为图的割点。 若图中不存在割点, 则该图为点双连通图。

如何判断割点?

若点 x 是树根, 且存在多个子节点, 则 x 是割点

若点 x 不是树根, 且存在一个子节点 y , 满足 $dfn \le low[y]$, 则 x 是割点。

割边

对于无向图中一条边, 若删掉该边后新图不连通, 则该边为图的割边, 也称作桥, 若图中不存在割边, 则该图为边双连通图

如何判断割边?
x 存在一个子节点 $y$, 满足 dfn[x] < low[y](x, y) 的连边是割边.

小组成员:邵品渊, 邵品砚, 蒋佳成, 顾天泽, 马天翔, 陈继禹

2 个赞