Tarjan
Tarjan 是一种可以求有向图强连通分量的算法,它能做到线性时间的复杂度。
学习之前要先记住两个关键数组:
low[i] 表示栈中元素可以到达 i 的最小位置(也就是最靠近栈底);
dfn[i] 表示 i 元素在遍历过程中的标号(就是dfs遍历,第几个遍历到);
个人认为这个算法最关键的是理解当 dfn[i]==low[i] 时,在 i 元素后面入栈的都是强连通分量(也可称为强连通子图);
可以先从没有出度的点开始理解,比如3号点,这个点的dfn[3]=3,low[3]=3,并且不会再往下遍历,也就是说3号点的dfn和low都不会再改变,说明dfn[3] == low[3],3号点就是一个单独的强连通分量;
而其它的各个点(除了一号点),他们虽然的刚开始的 low==dfn,但是之后会进行改变,直到一号点;
再说回为啥 i 元素后面入栈的都是强连通分量,可以这样想,i 后面的元素都是和 i 相连的(也可以说 i 可以到达它们),当 i 都可以自己到自己时,那 i 可以到的点是不是也可以到达 i 呢?
模板:
void Tarjan ( int x ) {
dfn[ x ] = ++dfs_num ;
low[ x ] = dfs_num ;
vis [ x ] = true ;//是否在栈中
stack [ ++top ] = x ;
for ( int i=head[ x ] ; i!=0 ; i=e[i].next ){
int temp = e[ i ].to ;
if ( !dfn[ temp ] ){
Tarjan ( temp ) ;
low[ x ] = gmin ( low[ x ] , low[ temp ] ) ;
}
else if ( vis[ temp ])low[ x ] = gmin ( low[ x ] , dfn[ temp ] ) ;
}
if ( dfn[ x ]==low[ x ] ) {//构成强连通分量
vis[ x ] = false ;
color[ x ] = ++col_num ;//染色
while ( stack[ top ] != x ) {//清空
color [stack[ top ]] = col_num ;
vis [ stack[ top-- ] ] = false ;
}
top -- ;
}
}
上一道模板题:
这道题只要记录大于2的环(强连通子图)的最小环;
代码:
#include<bits/stdc++.h>
#define LL long long
#define pa pair<int,int>
#define ls k<<1
#define rs k<<1|1
#define inf 0x3f3f3f3f
using namespace std;
const int N=200100;
const int M=1000100;
const LL mod=100000000;
int dfn[N],low[N],sta[N],tot,head[N],cnt,ans=2e9,top;
bool vis[N];
struct Node{
int to,nex;
}edge[N*2];
void add(int p,int q){
edge[cnt].to=q;
edge[cnt].nex=head[p];
head[p]=cnt++;
}
void Tarjan(int p){
dfn[p]=++tot,low[p]=tot;
if(!vis[p]) vis[p]=true,sta[++top]=p;
for(int i=head[p];~i;i=edge[i].nex){
int q=edge[i].to;
if(!dfn[q]){
Tarjan(q);
low[p]=min(low[p],low[q]);
}
else if(vis[q]) low[p]=min(low[p],dfn[q]);
}
if(dfn[p]==low[p]){
int sum=1;
vis[p]=false;
while(sta[top]!=p){
vis[sta[top]]=false;
sum++;
top--;
}
top--;
if(sum>1) ans=min(ans,sum);
}
}
int main(){
memset(head,-1,sizeof(head));
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
int t;
scanf("%d",&t);
add(i,t);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) Tarjan(i);
}
cout<<ans<<endl;
return 0;
}