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;
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;
}