Tarjan 强连通分量 + 解释

日常补东西 这次是Tarjan算法 原理还是别翻我这里了......但其他的倒是挺详细的~~

强连通分量

先给个题目 见下

题目大意:n (2 ≤ n ≤ 10000) 个点,m (2 ≤ m ≤ 50000) 条边 求大于1的强连通分量的个数

好了Tarjan算法走起

 

关于遍历

Tarjan 算法最主要的还是两个数组

dfn 用来表示当前点的dfs序编号 (同树剖那个id) 就是记录当前点是第几个被遍历到的

low 则表示当前点所在强连通分量的 ' 根 ' 就是该强连通分量的点中编号最小的那个 主要用途就是当 dfn[p] == low[p] 时说明该强连通分量搜完了 然后存下来

还有一个数组 (我个人叫他from) 用来存某点所在的强连通分量的编号 但本题用不到 这个更新...放在弹出栈的循环里

说到遍历的话 这里用栈(stack然而我嫌名字太长用que代替) 每搜到一个点就丢进去

如何遍历呢

先把最重要的 dfn 和 low 的 值赋了 一开始 low 的值 等于遍历的这个点的 dfn 值

因为一个点的 dfn 值 等于其 low 值 的时候 说明这是一个强连通分量的根(开始的时候是一样的 就是把每个点都当成一个强连通分量 之后再去搜索 合并)

然后我们进行dfs深度优先搜索 等到搜到之前的点了 说明此时的点就可以更新到之前某处的点 于是回溯之后该点的上一个点 上两个点 上三个点......搜到那个点了 就都能连到一块了 这样一个大的强连通分量就出来了

如果搜到最后没点继续往下走了 都散了散了 这里没有大于一的强连通分量

但所有的点都要搜到哇 没搜到的点的话还要继续哇 Tarjan 在不一定完全连通的图里面也是要用到的

于是用一个日常的 o 数组 判断是否找过 没找过就从那个点开始 tarjan 即可

遍历完了怎么办? 开始弹出该强连通分量的所有点 当然别忘了弹出根 建议用 do{} while() 然而我是用 while() 外面再加一次的

2018.12.15 Update : 打仙人掌的时候发现无向图用 Tarjan 的时候要判父亲不能回去 有向图无向图区别就这个

然后啥都没了

之后根据题目所需自己瞎搞搞就好 本题代码放下来 链接这里再放一个

#include <cstdio>
using namespace std;
const int MAXN = 10010;
const int MAXM = 100010;
struct edge {
	int to,next;
} e[MAXM];
int first[MAXN],dfn[MAXN],low[MAXN],que[MAXN],num[MAXN]/*,id[MAXN]*/;
int g,tot,t;
short o[MAXN];
int min(int x,int y) {return x < y ? x : y;}
void add(int x,int y)
{ //邻接存边哇
	e[++tot].next = first[x];
	e[tot].to = y;
	first[x] = tot;
}
void tarjan(int p)
{
	dfn[p] = ++tot; //存点遍历id
	low[p] = tot; //存点属于的强连通分量的根
	que[++t] = p; //栈 方便处理整个环
	o[p] = 1; //标记为访问过
	int b;
	for (int a = first[p] ; a ; a = e[a].next)
	{ //遍历
		b = e[a].to; //(下行)如果下面点没遍历过就dfs下去 然后low取最小看看是不是比自己的dfn小
		if (!dfn[b]) tarjan(b),low[p] = min(low[p],low[b]); else //如果小的话说明在一环中
		if (o[b]) low[p] = min(low[p],dfn[b]); //如果找到个环了直接更新成环顶 这个时候不要弹出来更新啊
	}
	if (dfn[p] == low[p]) //找到强连通分量的根了!然而有可能是单个点= =
	{ //通过上面的dfs可以保证绝对会转完一圈或者动都没动
		++g;//存强连通分量编号 有些dfn和low不等的点这里省去了
		while (que[t] != p) /*id[que[t]] = p,*/
		++num[g],o[que[t--]] = 0;//加p点为根的强连通分量的点数&弹出
		++num[g],o[que[t--]] = 0;//最后p点在里面没算就弹出来了要再弄一下
	}
}
int main()
{
	int n,m,x,y;
	scanf("%d%d",&n,&m); while (m--)
	scanf("%d%d",&x,&y),add(x,y);
	tot = 0; for (int a = 1 ; a <= n ; ++ a) if (!o[a]) tarjan(a); //万一图不连通= =
	tot = 0; for (int a = 1 ; a <= g ; ++ a) if (num[a] > 1) ++tot;
	printf("%d\n",tot);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值