強連通分量:
1)有向圖中,該圖中的任意兩點之間可互達。
2)一個一個點也是強連通分量
兩個概念:
1)時間戳 dfn[ x ]
時間戳是用來標記圖中每個節點在進行深度優先搜索時被訪問的時間順序,當然,你可以理解成一個序號(這個序號由小到 大),用 dfn[x] 來表示(搜到該點的最早時間)
2)low數組
2)low數組 : low[x] 表示 x是從哪個點(這個環中最早遍歷到的那個點的 low 值 ) 組成的環,如果這幾個點組成一個環,則這幾個點 low值 都是相同,因爲他們從某個點開始遍歷,最終還能回到這個點。
整體思想
每次搜一個結點 初始化 dfn [x] = low [x] = ++tot 如果該節點沒有被訪問過就DFS訪問他的出邊,然後併入棧,在回溯的過程中更新該點的low[x]值 low[x] = min(low[x], low[y] ),看x 和 y 都是以哪個點爲根節點的環。如果 y 點被訪問過,且還在棧中(說明這個點是強聯通分量的中的一個點)繼續更新low值 Low[ x ] = min(low[x], dfn[y]) 看誰出現的時間更早,不在棧中說明已經是其他或者自己成爲了一個強連通分量
回溯時 dfn [ x ] = low [ x ] 時 // x 這個點就是 強聯通分量的根,然後依次從棧內彈出元素,直到x == 棧頂元素(找到了環的根)就退出 找到了圖中的一個強聯通分量
代碼:
const int MAXN = 1e5 + 10;
struct Edge{
int to, next, dis;
}edge[MAXN << 1];
int head[MAXN], cnt, ans;
bool inStack[MAXN]; //判斷是否在棧中
//dfn 第一次訪問到該節點的時間(時間戳)
//low[i] low[i]能從哪個點(最早時間戳)到達這個點的。
int dfn[MAXN], low[MAXN], tot;
stack<int> stc;
void add_edge(int u, int v, int dis) {
edge[++cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt;
}
void Tarjan(int x) {
dfn[x] = low[x] = ++tot;
stc.push(x);
inStack[x] = 1;
for(int i = head[x]; i; i = edge[i].next) {
int to = edge[i].to;
if ( !dfn[to] ) {
Tarjan(to);
low[x] = min(low[x], low[to]);
} else if (inStack[to]){
low[x] = min(low[x], dfn[to]);
}
}
//cout << x << " " << low[x] << " " << dfn[x] << endl;
if(low[x] == dfn[x]) { //發現是整個強連通分量子樹裏 的最小根。
//int cnt = 0;
ans++; //強連通分量計數器
while(1) {
int top = stc.top();
stc.pop();
//cnt ++;
inStack[top] = 0;
//cout << top << " "; 每個強連通分量內的點
if(top == x) break;
}
}
}
void init() {
cnt = 1;
tot = 0;
ans = 0;
memset(inStack, 0, sizeof(inStack));
memset(head, 0, sizeof(head));
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
while(!stc.empty()) stc.pop();
}
int main () {
std::ios::sync_with_stdio(false);
cin.tie(0);
int n, m;
while(cin >> n >> m && (n || m)){
init();
int x, y;
for(int i = 1; i <= m; ++i) {
cin >> x >> y;
add_edge(x, y, 0); //有向圖求強連通
}
for(int i = 1; i <= n; ++i) {
if( !dfn[i] )
Tarjan(i);
}
}
return 0;
}
模板題: