【算法笔记】Tarjan的SCC算法


前言

我爱\cy Tarjan! Tarjan万岁!

(高中部的生活太美好,赛扩你high铁鸭子大~ \cy

一些基础

流图和搜索树

因为 \(\text{Tarjan}\)\(\text{SCC}\) 算法是基于 \(\text{dfs}\) 的。

所以需要一些关于“搜索树”的概念。

我们给定一张有向图 \(G=(V,E)\)

如果存在一个 \(r \in V\) ,使得从 \(r\) 出发能够到达 \(V\) 中其它的所有点。

那么我们称 \(G\) 是一张“流图”,记作 \((G,r)\)

那么对于流图 \((G,r)\) ,我们对它进行 \(\text{dfs}\),每个节点都只访问一次。

我们可以得到一颗搜索树 \(T\) ,它就是这个流图 \((G,r)\) 的搜索树。

那么在 \((G,r)\) 的所有有向边 \((x,y) ,x \to y\) 中会出现这样子的四种情况:

  1. 树边:搜索树 \(T\) 中的边。
  2. 正向边(前向边): 在搜索树中 \(x\)\(y\) 的祖先节点。
  3. 反向边(后向边): 在搜索树中 \(y\)\(x\) 的祖先节点。
  4. 交叉边(横叉边): 在搜索树过程中不同子树之间的边(也就是 \(dfn[y]\(dfn\) 是什么等会会说))。

WCYNrj.png

这是一个流图 \((G,r)\) 和它的搜索树 \(T\)

另外,在执行 \(\text{dfs}\) 的过程当中,我们按照 访问 的次序以此给每一个节点 \(u\) 打上一个标记 \(dfn[u]\)\(dfn[u] \in N_+ \, ,\, dfn[u] \in [1,n]\))。

这个标记 \(dfn\) 被称作“时间戳”。

强连通分量(Strongly Connected Component)

定义:

在有向图 \(G\) 中,如果任意两个顶点都是连通的,那么这个图就是强连通图。

非强连通图的极大强连通子图,被称为强连通分量。

下图中的子图 $ {1,2,3,5}$ 就是一个极大强连通子图,子图 \(\{4\}\) 也是一个极大强连通子图。

对于“极大”的理解,就是在一个局部子图中不能再大。

WCUWZj.png

另外有一个理解:

每一个点都只能在一个 SCC 当中。

如果你一个点连接着两个不同的强连通分量(他们实际上应该是强连通子图)。

像下图那样,那么这两个强连通子图和这个点一定会构成一个新的更大的 SCC。

WCd6bQ.png

Tarjan的线性SCC算法

基础思想

你会发现,如果在 \(G\) 当中出现了一个 “环”,那么这个“环”一定是一个强连通子图。

如果想要求强联通分量,那么我们就要尽可能地扩大这个强连通子图。

思考一下,如果要构成一个环,那么反向边是一定有用的,交叉边有时也有用(能在图 \(G\) 中找到回到在搜索树中是祖先的节点的路的时候)。

所以 \(\text{Tarjan}\)的基本思路就是找到尽量多的环。

那么就需要找到反向边和交叉边,这时 \(\text{Tarjan}\)就引入了一个叫“追溯值 \(low[u]\)”的东西。

\(low[u]\) 的定义:

\(low[u]\) 是所有满足一下两个条件节点 \(v\) 的最小时间戳的值。

  1. 这个点在搜索时建立的栈中(目前阶段被访问过了)
  2. 存在一条从 \(u\) 的子树中出发的有向边,终点是这个节点 \(v\)

也就是以 \(u\) 为起点,\(u\)\(u\) 的子树能够追溯到的最早的栈中节点的 \(dfn\)

说白了就是要尽量快地找到更多“环”。

至于这个栈是干什么的?

它在搜索时,边搜边记录所有当前阶段已经被访问过的节点。

用栈是为了符合 \(\text{dfs}\) 的特点。

流程

  1. 当节点 \(u\) 第一次被访问时,入栈,标记在栈里面。并且初始化 \(low[u]=dfn[u]\)

  2. \(u\) 出发,扫描所有出边 \((u,v)\)

  3. 如果 \(v\) 没有被访问过,那么 \((u,v)\) 是树边,递归去搜 \(v\),回溯之后令 \(low[u]=\min(low[u],low[v])\)

  4. 反之,如果 \(v\) 已经被访问过而且在栈之中,令 \(low[u]=\min(low[u],dfn[v])\)

(为什么是 \(dfn[v]\) 请看 Tarjan 的论文,反正只能是这样子(有时不会出错但是不严谨))

然后如果 \(low[u]=dfn[u]\) ,那么就找到了一个 \(\text{SCC}\) ,记录,然后把栈中节点一直弹栈并加入 \(\text{SCC}\) ,直到 \(u\) 出栈 (这里就可以看出来为什么要用栈)

\(low[u]=dfn[u]\) ,也就是以 \(u\) 为根的搜索子树的所有栈中节点能够到达的最早节点是 \(u\) 本身。

为什么这个时候就一定是一个强连通分量了呢?

假设这个SCC里面有一个节点 \(v\),但是它不属于 \(u\) 为根的搜索子树的所有栈中节点所构成的集合 \(S\)

那么也就是说在 \(u \to v\) 的路径上面,一定有能够离开子树的一条反向边(去到比 \(u\) 更早访问的节点),或者交叉边(从这个子树跳到另一个子树)。

这个边的终点的 \(dfn\) 就必须比 \(dfn[u]\) 小,和上面的条件:

“能够到达的最早节点是 \(u\)。”

矛盾了,那么此时无法构成SCC,所以这样的 \(v\) 不存在。

那么很容易得知此时(\(low[u]=dfn[u]\) )已经构成了一个SCC。

Code:

(缩点 & SCC)


#include
using namespace std;

const int si_n=1e5+10;
const int si_m=1e6+10;
#define pb push_back

struct Edge{
	int ver,Next,head;
}e[si_m];
int cnt_e=0;
void add(int u,int v){
	e[++cnt_e].ver=v,e[cnt_e].Next=e[u].head;
	e[u].head=cnt_e;
}

stacks;
bool ins[si_n]; // in the stack or not
int c[si_n]; //c[x] = the num of SCC which is x iside
vectorscc[si_n]; // scc[i] -> all node in i-th scc (information of i-th scc)
int dfn[si_n],low[si_n];
int n,m,cnt_t=0,tot=0; // tot = how many scc in the graph. 

void tarjan(int u){
	dfn[u]=low[u]=++cnt_t;
	s.push(u),ins[u]=true;
	for(register int i=e[u].head;i;i=e[i].Next){
		int v=e[i].ver;
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(ins[v]) low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		++tot;
		int x;
		do{
			x=s.top(),s.pop(),ins[x]=false;
			c[x]=tot,scc[tot].pb(x);
		}while(u!=x);
	}
}

Edge edag[si_m];//新图
int cnt_d=0;
void add_n(int u,int v){
	edag[++cnt_d].ver=v,edag[cnt_d].Next=edag[u].head;
	edag[u].head=cnt_d;
}
void contract(){
	for(register int u=1;u<=n;++u){
		for(register int i=e[u].head;i;i=e[i].Next){
			int v=e[i].ver;
			if(c[u]==c[v]) continue;
			add_n(c[u],c[v]);
		}
	}
}//缩点

int main(){
	cin>>n>>m;
	for(register int i=1,u,v;i<=m;++i){
		cin>>u>>v;
		add(u,v);
	}
	for(register int i=1;i<=n;++i){
		if(!dfn[i]) tarjan(i);
	}
	//......
}

注意在使用的时候可以视情况去掉 vectorscc 一类的东西来节省空间。

缩点就是直接把每一个 SCC 缩成一个新点(这时可以缩出一个DAG,建新图)

例题:

  • P3387 【模板】缩点

  • B3609 [图论与代数结构 701] 强连通分量

  • P2863 [USACO06JAN]The Cow Prom S

以上为模板题。

  • P2272 [ZJOI2007]最大半连通子图

  • P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G

  • P2921 [USACO08DEC]Trick or Treat on the Farm G

  • P3627 [APIO2009]抢掠计划

  • P2746 [USACO5.3]校园网Network of Schools

  • P2515 [HAOI2010]软件安装

  • P2403 [SDOI2010]所驼门王的宝藏

  • P3119 [USACO15JAN]Grass Cownoisseur G

相关