题目抽象
求有多少个结点对能够互相到达
大致思路
思路一【50分】:对每个结点DFS,求传递闭包,时间为O(V*E),代码简单,但是超时
思路二【100分】:计算图的强连通分量(SCC),各个分量里面的点都是可以相互到达的
SCC算法:O(V+E)
强连通分量(Strongly Connected Components)
①DFS求拓扑序(按u.f升序排列)
②将图转置,按拓扑序的逆序DFS,得到的就是SCC
C++满分代码(带注释)
#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
#include <list>
#define endl "\n"
using namespace std;
vector<int> topo;
vector<list<int> > adj,tmp;
int n,m,a,b;
bool vis[10005];
vector<int> cnt; //各个SCC的结点个数
void dfs1(int x)
{
for(list<int>::iterator p = adj[x].begin(); p != adj[x].end(); ++p)
{
if(vis[*p] == 0)
{
vis[*p] = 1;
dfs1(*p);
}
}
topo.push_back(x); //完成搜索,即结点变为黑色时,加入拓扑序
}
void dfs2(int x)
{
for(list<int>::iterator p = tmp[x].begin(); p != tmp[x].end(); ++p)
{
if(vis[*p] == 0)
{
vis[*p] = 1;
++cnt.back(); //增加当前SCC的结点个数
dfs2(*p);
}
}
}
inline int compute(int a) //组合数C(n, 2)
{
if(a<2) return 0;
return a*(a-1)/2;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
adj.assign(n+1,list<int>());
tmp.assign(n+1,list<int>());
for(int i=0; i<m; ++i) //建立邻接表adj和邻接表的转置tmp
{
cin>>a>>b;
adj[a].push_back(b);
tmp[b].push_back(a);
}
memset(vis,0,sizeof(vis)); //第一次dfs,记录"拓扑序"
for(int i=1; i<=n; ++i)
{
if(vis[i]==0)
{
vis[i] = 1;
dfs1(i);
}
}
memset(vis,0,sizeof(vis)); //第二次dfs,求SCC
for(int i=n-1; i>=0; --i)
{
int node = topo[i];
if(vis[node]==0)
{
vis[node] = 1;
cnt.push_back(1);
dfs2(node);
}
}
int sum = 0;
for(int i=0; i<cnt.size(); ++i) //计算答案
sum += compute(cnt[i]);
cout<<sum;
return 0;
}