1. 程式人生 > >Luogu p2683 牛的舞會題解/Tarjan模板

Luogu p2683 牛的舞會題解/Tarjan模板

題目連結

Tarjan演算法

這是一個求一個圖中的強連通分量的演算法。強連通分量是指這一個子圖中所有節點都能互相到達。當然,Tarjan的效率很高,時間複雜度為O(n+m)。

這裡我們要用到兩個關鍵的陣列,一個是dfn,簡稱豆腐腦,是記錄每一個節點在dfs中是第幾個被搜到的(時間戳),還有一個就是low,代表了每一個節點的根,就是它的強連通分量的起始點。

當一個點沒有被搜到過,就繼續往下搜,並且更新走到自己身上的點的dfn。當一個點第二次被找到時,證明它已經在一個強連通分量裡了,所以就要認找到你的人為根(更新low)

當一個點一次dfs過後還沒被搜到,就以這個點繼續搜。

具體程式碼如下

#include
<bits/stdc++.h>
using namespace std; inline int read(){ int s=0,f=1;char c=getchar(); for(;!isdigit(c);c=getchar())if(c=='-')f=-1; for(;isdigit(c);c=getchar())s=(s<<3)+(s<<1)+(c^48); return s*f; } inline void write(int x){ if(x<0){putchar('-');x=-x;} if(x>9) write(x/10); putchar
(x%10+'0'); } //讀入輸出優化 int dfn[10001],low[10001],n,m,ans=0; //dfn:每個點第幾個被搜到的(時間戳) //low: 作為每個點在這顆樹中的,最小的子樹的根,每次保證最小,like它的父親結點的時間戳這種感覺。如果它自己的LOW[]最小,那這個點就應該重新分配,變成這個強連通分量子樹的根節點。 int visit[10001],tot; //visit:有沒有被搜到過的標記。tot:現在是第幾個單位時間 vector<int>v[10001];//鄰接表 stack<int>s;//棧 void tarjan(int x) { dfn[
x]=low[x]=++tot; //將找到的這個點的dfn和low都改成 當前時間戳。 s.push(x); //入棧 visit[x]=1;//標記 for(int i=0;i<v[x].size();i++) { if(!dfn[v[x][i]]) { //當前點沒有被找到過(沒有定義過dfn等同於沒有被找到過) tarjan(v[x][i]); //繼續往下搜 low[x]=min(low[x],low[v[x][i]]); //更新low } else if(visit[v[x][i]]){ //當前點被找到過了 low[x]=min(low[x],dfn[v[x][i]]); //用搜到的點的dfn去更新當前點的low } } if(low[x]==dfn[x]) //如果當前點就是自己的根 { int t=0; while(s.top()!=x){ t++; visit[s.top()]=0;s.pop(); }//出棧 s.pop();visit[x]=0; if(t)ans++;//強連通分量個數加一 } return; } int main() { n=read();m=read(); for(int i=1;i<=m;i++) { int x=read(),y=read(); v[x].push_back(y);//建鄰接表 } for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);//如果當前點沒被找到過就以當前點為中心去tarjan printf("%d\n",ans); return 0; }