算法
(kosaraju算法)
听了y总的建议,看了算法导论,学到的新算法,写一篇题解纪念一下。
算法流程:
使用这个算法,需要建两个图,一个是原图,另一个是转置图(就是把边反向的图)
对于每个节点记录两个值,di,fi
di:是这个点第一次访问到的序号
fi:是这个点的所有子节点访问完成之后的序号,和di对应,就是这个点完成搜索的序号
首先dfs遍历原图一遍,求出每个节点的di和fi,同时按照fi从小到大将节点一次入栈
之后从栈中依次取出每一个节点,遍历转置图一遍,这个节点所能到达的所有节点就是一个强连通分量
因为先完成的f小,又恰好要求f小的先入栈,所以在入栈的时候,按照完成的顺序入栈,先完成的先入栈,后完成的后入栈。
时间复杂度
$O(V+E)$ ,因为要dfs两边,所以效率低于tarjan算法
参考文献
《算法导论》第三版
C++ 代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 10010, M = 100010;
int h[N], hs[N], e[M], ne[M], idx;
int n, m;
int d[N], f[N], timestamp;
bool st[N];
int stk[N], top;
int dout[N];
int sz[N];
int scc_cnt, id[N];
void add(int h[], int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs1(int u)
{
st[u] = true;
d[u] = ++ timestamp;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!st[j])
dfs1(j);
}
f[u] = ++ timestamp;
stk[ ++ top] = u;
}
void dfs2(int u)
{
id[u] = scc_cnt;
sz[scc_cnt] ++ ;
st[u] = true;
for (int i = hs[u]; ~i; i = ne[i])
{
int j = e[i];
if (!st[j])
dfs2(j);
}
}
int main()
{
memset(h, -1, sizeof h);
memset(hs, -1, sizeof hs);
cin >> n >> m;
for (int i = 0; i < m; i ++ )
{
int a, b;
cin >> a >> b;
add(h, a, b);
add(hs, b, a);
}
for (int i = 1; i <= n; i ++ )
if (!st[i])
dfs1(i);
memset(st, 0, sizeof st);
while (top)
{
int t = stk[top -- ];
if (!st[t])
{
++ scc_cnt;
dfs2(t);
}
}
int zeros = 0, res = 0;
for (int u = 1; u <= n; u ++ )
for (int i = h[u]; ~i; i = ne[i])
{
int v = e[i];
int a = id[u], b = id[v];
if (a != b)
dout[a] ++ ;
}
for (int i = 1; i <= scc_cnt; i ++ )
if (!dout[i])
{
res += sz[i];
zeros ++;
if (zeros > 1)
res = 0;
}
cout << res << endl;
return 0;
}
效率低但是好写啊哈哈
d 没有意义。不需要
弔