1.前置知识
-
强联通:若两个点可以互相通达,则称这两个点强联通。
-
强连通图:若图中每两个点都强联通,则称这个图为强连通图。
-
强联通分量:若图中有一个子图中每两个点都强连通,则称这个子图为强连通分量。
如图中 ({1,2,3,4})、({5})、({6}) 即为强连通分量
- 链式前向星
可以阅读https://blog.csdn.net/sugarbliss/article/details/86495945。
2.Tarjan
Tarjan是一种用来求强连通分量的算法。
我们用一个数组 (dfn) 来存当前点的编号,用一个数组 (low) 来存当前点能到达的点的最小编号,用一个数组 (vis) 来表示当前点是否被访问过。
然后进行 ( exttt{DFS})。每遍历到一个点就让它入栈,并且把 (vis_{i}) 赋为 ( exttt{true}),表示当前点被遍历过且在栈内。
如果下一个要遍历的点没被遍历过,那么先遍历下一个点,然后把当前的 (low_{i}) 更新为 (min(low_{i},low_{r[i]}))。((r_{i}) 表示下一个要遍历的点)
如果下一个要遍历的点被遍历过且还在栈内,那么把当前的 (low_{i}) 更新为 (min(low_{i},dfn_{r[i]}))。
举个例子,如图。
从(1)开始 ( exttt{DFS}),将 (1) 入栈,(dfn_{1}=1,low_{1}=1)。
( exttt{DFS}) (3),未被遍历过,将 (3) 入栈,(dfn_{3}=2,low_{3}=2)。
(3) 不能继续 ( exttt{DFS}),将 (3) 出栈。
( exttt{DFS}) (4),未被遍历过,将 (4) 入栈,(dfn_{4}=3,low_{4}=3)。
( exttt{DFS}) (2),未被遍历过,将 (2) 入栈,(dfn_{2}=4,low_{2}=4)。
( exttt{DFS}) (1),已被遍历,(low_{2}=min(low_{2},dfn_{1})=1)。
返回 (4),(low_{4}=1)。
返回 (1)。
( exttt{DFS}) (8),未被遍历过,将 (8) 入栈,(dfn_{8}=5,low_{8}=5)。
( exttt{DFS}) (6),未被遍历过,将 (6) 入栈,(dfn_{6}=6,low_{6}=6)。
( exttt{DFS}) (5),未被遍历过,将 (5) 入栈,(dfn_{5}=7,low_{5}=7)。
( exttt{DFS}) (9),未被遍历过,将 (9) 入栈,(dfn_{9}=8,low_{9}=8)。
( exttt{DFS}) (6),已被遍历,(low_{9}=min(low_{9},dfn_{6})=6)。
返回 (5)。
返回 (6),(low_{6}=5)。
返回 (8)。
(9) 出栈。
(5) 出栈。
(6) 出栈。
(8) 出栈。
(2) 出栈。
(4) 出栈。
(1) 出栈。
( exttt{DFS}) (7),未被遍历过,将 (7) 入栈,(dfn_{7}=9,low_{7}=9)。
(7) 出栈。
( exttt{tarjan}) 结束。
如果不懂,可以结合以下动图食用。
3.总结
例题:P2661。
题意:图中的每一个点都连有一条有向边 ((i,t_{i})),求图中最小环。
解答:考虑在 ( exttt{tarjan}) 过程中求最小环,如果当前是一个环,则打擂台找最小值,注意排除自环。
#include<bits/stdc++.h>
using namespace std;
int n,ans=0x3ffffff;
int a;
int sta[200002],top;//查询数组
int dfn[200002],low[200002],tot;//tarjan数组
int head[200002],Next[200002],to[200002],cnt;//链式前向星数组
int f[200002];//栈
void addedge(int x,int y)//链式前向星
{
cnt++;
Next[cnt]=head[x];
head[x]=cnt;
to[cnt]=y;
}
void tarjan(int u)
{
f[u]=1;//标记为已入栈
dfn[u]=low[u]=++tot;//记录dfn和low
sta[++top]=u;//记录,便于查询
for(int i=head[u];i;i=Next[i])
if(!dfn[to[i]]) {tarjan(to[i]);low[u]=min(low[u],low[to[i]]);}//如果该点未被访问,则对此点进行tarjan
else if(f[to[i]]) low[u]=min(low[u],dfn[to[i]]);//否则更新low
if(low[u]==dfn[u])//如果找到环
{
int res=0;
do
{
f[sta[top]]=0;//出栈
res++;
}while(sta[top--]!=u);//找出当前环的点数
if(res!=1) ans=min(ans,res);//寻找最小值
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) {cin>>a;addedge(i,a);}//存图
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);//如果该点未被访问,则对此点进行tarjan
cout<<ans;
return 0;
}
图论基础。
参考资料:
图论——(color{black}Ocolor{red}RzyzRO)