OJ 1340. Strongly Connected
题目描述
给定一张 n 个点,m 条边的有向图,询问有多少对点 ( u , v ) (u,v) (u,v) 满足:
- 存在一条从 u 到 v 的路径;
- 存在一条从 v 到 u 的路径;
- u < v。
Input
请从 stdin 读入。
输入第一行为两个个正整数 n, m
(
1
≤
n
,
m
≤
1
0
5
)
(1≤n,m≤10^5)
(1≤n,m≤105)。
接下来 m 行,第 i 行为用空格隔开的整数
u
i
u_i
ui,
v
i
v_i
vi
(
1
≤
u
i
,
v
i
≤
n
)
(1≤u_i,v_i≤n)
(1≤ui,vi≤n),表示第 i 条边为从
u
i
u_i
ui 到
v
i
v_i
vi 。
输入可能存在重边与自环。
对于 30 分的数据,n≤500。
Output
请输出到 stdout 中。
输出一行,包含一个整数,表示你的答案。
Sample Input
case 1
4 4
1 2
2 3
3 4
4 1
case 2
4 4
1 2
2 3
3 4
1 4
case 3
4 4
1 1
2 3
2 3
3 2
case 4
5 5
1 1
2 2
1 2
1 3
1 4
case 5
4 4
4 1
4 2
4 3
3 4
Sample Output
case 1
6
case 2
0
case 3
1
case 4
0
case 5
1
Solution
使用 DFS(deep first search) 求解有向图 g 的强连通分量(strong connected component, SCC)。
首先证明如果将每个强连通分量当成一个点的话,有向图所对应的SCC点图一定是有向无环图。因为如果有环的话意味着这个环上的SCC点彼此连通,它们可以组成一个更大的SCC。
利用上述结论即可使用DFS求解有向图 g 的所有强连通分量。先引入图的头结点和尾结点的概念。用 DFS 算法遍历原图并得到每个点的finish时间(即在DFS递归中返回的时间顺序),头结点是指原图中 finish 时间最大的点,尾结点是指原图中 finish 时间最小的点。例如,gr 是一棵树,gr 的头结点便是它的根,因为根在 DFS 递归中最后返回。gr 的尾结点是在DFS递归中最先返回的某个叶节点。
由于原图对应 SCC 图是有向无环图,只需找到原图中的尾结点,并以尾结点为起始点 DFS 原图,就可以剥离出SCC图中的尾结点,即原图中尾部的强连通分量。将SCC图中的尾结点(一个SCC)所对应的所有点在原图中剔除(可以设置visited数组记录)得到新图,不断重复寻找新图尾结点,DFS 分离SCC 操作,最终得到原图所有的SCC分量。之后的工作便十分容易,只需 count 所有SCC分量中满足题意的点对数即可。具体操作如下:
用二维 vector 来存储原图 g 并构造反图 gr (点不变,有向边反向)。用 DFS 算法遍历 gr 得到 gr 中 finish 时间最大的点,即 gr 中的头节点。gr 中的头节点一定是 g 中的尾结点,因为 g 也是 gr 的反图。 故只需找到 g 中的尾节点,然后不断地找尾结点,DFS 分离 SCC 操作,得到原图 g 中所有的SCC分量。最后 count 所有SCC分量中满足题意的点对数即可。
Code
#include<iostream>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;
vector<vector<int>> g;
vector<vector<int>> gr;//反图
vector<bool> visited;
vector<bool> visited_r;
stack<int> sg;
int sum = 0;
long long ans = 0;
void explore(int start){
visited_r[start] = true;
for(int i=0;i<gr[start].size();i++){
if(!visited_r[gr[start][i]]){
explore(gr[start][i]);
}
}
sg.push(start);
}
void dfsr(){
for(int start=1;start<visited_r.size();start++){
if(!visited_r[start]){
explore(start);
}
}
}
void dfs(int start){
visited[start] = true;
sum++;
for(int i=0;i<g[start].size();i++){
if(!visited[g[start][i]]){
dfs(g[start][i]);
}
}
}
int main()
{
int n,m;
cin>>n>>m;
g.assign(n+1,vector<int>());
gr.assign(n+1,vector<int>());
visited.assign(n+1,false);
visited[0] = true;
visited_r.assign(n+1,false);
visited_r[0] = true;
int u,v;
for(int i=0;i<m;i++){
cin>>u>>v;
if(u==v){
continue;
}
else{
if(count(g[u].begin(),g[u].end(),v)>=1){
continue;
}
else{
g[u].push_back(v);
gr[v].push_back(u);
}
}
}
dfsr();
while(!sg.empty()){
int start = sg.top();
sg.pop();
if(!visited[start]){
sum = 0;
dfs(start);
if(sum<=1){
continue;
}
else{
ans = ans + (long long)((0.5*sum)*(sum-1));
}
}
}
cout<<ans;
return 0;
}