强连通分量:
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;
}
模板题: