Tarjan算法(OJ3899)

来源:互联网 发布:北京云计算公司 编辑:程序博客网 时间:2024/05/18 19:36

TarJan算法概念

如果两个顶点可以相互通达,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。
下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。
Tarjan算法是用来求有向图的强连通分量的。求有向图的强连通分量的Tarjan算法是以其发明者Robert Tarjan命名的。Robert Tarjan还发明了求双连通分量的Tarjan算法。
Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。
当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。

题目

Description
假如有命题p 一定能推出命题q,则称p 是q 的充分条件,q 是p 的必要条件。
特别的,当p 既是q 的充分条件,又是q 的必要条件时,称p 和q 互为充要条件
现在有n 个命题,其中一些是另一些的充分条件。请问有多少对命题互为充要条件?

Input
第一行三个正整数n,m,分别表示命题数、已知关系数
接下来m 行,每行两个正整数p 和q,表示命题p 是命题q 的充分条件

Output
仅一行,一个整数,表示充要条件的对数

Sample Input
5 5
1 3
3 2
2 1
4 5
5 4

Sample Output
4
样例说明:
4 对充要条件分别是(1, 2)、(2, 3)、(1, 3)、(4, 5)

Data Constraint
对于10% 的数据,n <= 10;m <= 50
对于40% 的数据,n <= 500;m <= 1000
对于另外10% 的数据,数据中保证没有重边且m = n^2
对于100% 的数据,n<= 50000;m <= 600000

题目分析

题目要求求出一共有多少对强连通的顶点。
很显然可以想到,枚举两个顶点再进行判断。
也很显然会超时。

思考

强连通图中顶点两两都可以强连通。
所以如果一个强连通图中的节点个数为X,那么一共有X*(X-1)对强连通顶点。
为了避免少算,强连通图一定要最大,也就是强连通分量
Tarjan算法即可。

实现

定义DFN[i]表示最早访问i节点的时间(第几个被访问到的)
LOW[i]表示以i为根的子树中最小的DFN。
当节点i第一次被访问到时,LOW[i]=DFN[i]
当DFN[i]=LOW[i]时,表示当前节点已经无法继续往下扩展了,所以已经构成了一个强连通分量。
节点i每搜索一次它的孩子节点时,只要孩子节点在栈中,就可以更新LOW[i]。
之后不断退栈,一直退到栈顶不是当前强连通分量中的节点。
栈中退出的节点就可以组成一个强连通分量。

代码

procedure tarjan(t:longint);var        i:longint;begin        inc(l);        d[l]:=t;        inc(now);        dfn[t]:=now;//初始化节点        low[t]:=now; //初始化节点        for i:=c[t] to c[t+1]-1 do        begin                if dfn[a[i,2]]=0 then  //如果该节点没有被遍历,就继续往下走                tarjan(a[i,2]);                if bz[a[i,2]]=false then //如果该节点未出栈,就更新LOW                low[t]:=min(low[t],low[a[i,2]]);        end;        if dfn[t]=low[t] then //如果已经构成一个强连通分量        begin                s:=l;                while d[l+1]<>t do //退栈,直到不在当前强连通分量                begin                        bz[d[l]]:=true; //标志已退栈                        dec(l);                end;                s:=s-l;                ans:=ans+s*(s-1) div 2; //计算总和(题目要求)        end;end;
1 0
原创粉丝点击