参考
Kosaraju
https://www.cnblogs.com/nullzx/p/6437926.html
tarjan
https://blog.csdn.net/qq_34374664/article/details/77488976
思路
求回路也就是求图的强连通分量
共有两种算法:Kosaraju与tarjan,其中Kosaraju算法较易理解但需要两次dfs,tarjan算法较难理解但只需dfs一次。
两种算法的详解见参考。
概述:
Kosaraju
1、对图的反向图进行一次逆后序dfs遍历
2、按照逆后序遍历得到的栈中点的出栈顺序对原图进行dfs遍历,每一次遍历访问到的点就属于一个强连通分量。
tarjan
1 /* 2 dfn为访问序号(时间戳) 3 low为当前节点及其子树中结点最小的访问序号,类似并查集 4 */ 5 void tarjan(int u){ 6 7 DFN[u]=Low[u]=++Index // 为节点u设定次序编号和Low初值 8 9 Stack.push(u) // 将节点u压入栈中 10 11 for each (u, v) in E // 枚举每一条边 12 { 13 if (v is not visted) // 如果节点v未被访问过 14 15 { 16 tarjan(v) // 继续向下找 17 Low[u] = min(Low[u], Low[v]) 18 } 19 20 else if (v in S) // 如果节点u还在栈内 21 22 { Low[u] = min(Low[u], DFN[v]) } 23 } 24 25 if (DFN[u] == Low[u]) // 如果节点u是强连通分量的根 26 { 27 repeat 28 29 v = S.pop // 将v退栈,为该强连通分量中一个顶点 30 31 print v 32 33 until (u== v) 34 } 35 36 }
实现
Kosaraju
1 //Kosaraju 2 #include<bits/stdc++.h> 3 4 using namespace std; 5 6 #define MAXN 10005 7 8 vector<int> Map1[MAXN]; 9 vector<int> Map2[MAXN]; 10 stack<int> sta; 11 int vis[MAXN]; 12 long long int ans=0,num=0; 13 int n,m; 14 15 int size,v; 16 void dfs2(int s){ 17 18 for(int i=0;i<Map2[s].size();i++){ 19 v=Map2[s][i]; 20 if(vis[v]==0){ 21 vis[v]=1; 22 dfs2(v); 23 } 24 } 25 sta.push(s); 26 } 27 28 void dfs1(int s){ 29 for(int i=0;i<Map1[s].size();i++){ 30 v=Map1[s][i]; 31 if(vis[v]==0){ 32 //cout<<v<<' '; 33 vis[v]=1; 34 num++; 35 dfs1(v); 36 } 37 } 38 } 39 40 long long int C(int x){ 41 if(x<=1){ 42 return 0; 43 } 44 else{ 45 long long int a=1; 46 for(int i=x;i>x-2;i--){ 47 a*=i; 48 } 49 return a/2; 50 } 51 } 52 53 int main(){ 54 cin>>n>>m; 55 int a,b; 56 for(int i=0;i<m;i++){ 57 cin>>a>>b; 58 Map1[a].push_back(b); 59 Map2[b].push_back(a); 60 } 61 62 memset(vis,0,sizeof(vis)); 63 for(int i=0;i<=n;i++){ 64 if(vis[i]==0){ 65 vis[i]=1; 66 dfs2(i); 67 } 68 } 69 70 memset(vis,0,sizeof(vis)); 71 int v; 72 while(!sta.empty()){ 73 v=sta.top(); 74 sta.pop(); 75 76 if(vis[v]==0){ 77 //cout<<v<<' '; 78 vis[v]=1; 79 num=1; 80 dfs1(v); 81 //cout<<num<<endl; 82 ans+=C(num); 83 } 84 } 85 86 cout<<ans; 87 88 return 0; 89 }
tarjan
1 //tarjan 2 3 #include<bits/stdc++.h> 4 5 using namespace std; 6 7 #define MAXN 10005 8 9 vector<int> Map[MAXN]; 10 stack<int> sta; 11 int vis[MAXN]; 12 int dfn[MAXN]; 13 int low[MAXN]; 14 int insta[MAXN]; 15 long long int num=0,ans=0; 16 int n,m,index; 17 18 long long int C(int x){ 19 if(x<=1){ 20 return 0; 21 } 22 else{ 23 long long int a=1; 24 for(int i=x;i>x-2;i--){ 25 a*=i; 26 } 27 return a/2; 28 } 29 } 30 31 void tarjan(int u) 32 { 33 index++; 34 dfn[u]=low[u]=index; 35 vis[u]=1; 36 sta.push(u); 37 insta[u]=1; 38 int size=Map[u].size(); 39 for(int i=0;i<size;i++){ 40 int v=Map[u][i]; 41 if(vis[v]==0){ 42 tarjan(v); 43 low[u]=min(low[u],low[v]); 44 } 45 else if(insta[v]==1){ 46 low[u]=min(low[u],dfn[v]); 47 } 48 } 49 num=0; 50 if(dfn[u]==low[u]){ 51 int v; 52 do{ 53 v=sta.top(); 54 //cout<<v<<' '; 55 sta.pop(); 56 insta[v]=0; 57 num++; 58 }while(v!=u); 59 //cout<<endl; 60 } 61 ans+=C(num); 62 } 63 64 int main(){ 65 cin>>n>>m; 66 int a,b; 67 for(int i=0;i<m;i++){ 68 cin>>a>>b; 69 Map[a].push_back(b); 70 } 71 memset(vis,0,sizeof(vis)); 72 memset(insta,0,sizeof(insta)); 73 index=0; 74 for(int i=1;i<=n;i++){ 75 if(vis[i]==0){ 76 tarjan(i); 77 } 78 } 79 cout<<ans; 80 81 return 0; 82 }
题目
问题描述
某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路。
现在,大臣们帮国王拟了一个修高速公路的计划。看了计划后,国王发现,有些城市之间可以通过高速公路直接(不经过其他城市)或间接(经过一个或多个其他城市)到达,而有的却不能。如果城市A可以通过高速公路到达城市B,而且城市B也可以通过高速公路到达城市A,则这两个城市被称为便利城市对。 国王想知道,在大臣们给他的计划中,有多少个便利城市对。 输入格式
输入的第一行包含两个整数n, m,分别表示城市和单向高速公路的数量。
接下来m行,每行两个整数a, b,表示城市a有一条单向的高速公路连向城市b。 输出格式
输出一行,包含一个整数,表示便利城市对的数量。
样例输入
5 5
1 2 2 3 3 4 4 2 3 5 样例输出
3
样例说明
城市间的连接如图所示。有3个便利城市对,它们分别是(2, 3), (2, 4), (3, 4),请注意(2, 3)和(3, 2)看成同一个便利城市对。 评测用例规模与约定
前30%的评测用例满足1 ≤ n ≤ 100, 1 ≤ m ≤ 1000;
前60%的评测用例满足1 ≤ n ≤ 1000, 1 ≤ m ≤ 10000; 所有评测用例满足1 ≤ n ≤ 10000, 1 ≤ m ≤ 100000。 |