这道题目很显然和传递闭包有关,要求出有多少个便利城市对,即求出图中相互可达的结点对的数量,如果我们有传递闭包,则可以迅速求出来。求传递闭包的算法有不少,包括floyd算法、dfs算法等。
然而传递闭包算法的效率不够高、不足以满分通过。再分析题目发现,其实并不需要求出传递闭包,而只需要进行强连通分量的划分即可。使用tarjan算法可以达到线性复杂度,是这道题目的最好解决方法。
下面这几种算法都将尝试一下,还可以对比它们的效率。
floyd算法的主体是一个三重循环,时间复杂度为O(n^3),因此很容易超时,提交后得到40分,结果显示运行超时。
关于传递闭包算法需要注意的一点是,默认c[i][i]是true,即任何点对其自身是可达的。
#include
#include
#include
using namespace std;
const int MAXN = 10010;
bool c[MAXN][MAXN];
int main(){
int n, m;
cin >> n >> m;
int u, v;
for (int i = 1; i <= n; i++)
c[i][i] = true; //对自身可达
for (int i = 1; i <= m; i++){
cin >> u >> v;
c[u][v] = true;
}
for (int k = 1; k <= n; k++) //floyd算法的三重循环
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
c[i][j] |= (c[i][k] && c[k][j]);
int cnt = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (c[i][j] && c[j][i])
cnt++;
cout << (cnt - n) / 2; //自己不能与自己成对,故减去n
return 0;
}
众所周知,从一个点s出发运行dfs,可以走到所有s可达的点。故要求出传递闭包,只需要对所有点运行dfs,算法的复杂度是O(n*(n+m))。
这个算法依然超时,不过拿了60分,说明效率比floyd算法高。
#include
#include
using namespace std;
//这个程序只能拿60分,原因是运行超时 (好在证明思路是正确的)
int n, m;
const int MAXV = 10010;
vector<vector<int> > adj(MAXV);
bool vis[MAXV];
bool c[MAXV][MAXV];
void dfs(int s, int u){
c[s][u] = true;
for (auto v : adj[u]){
if (!vis[v]){
vis[v] = true;
dfs(s, v);
}
}
}
int main(){
cin >> n >> m;
int u, v;
for (int i = 1; i <= m; i++){
cin >> u >> v;
adj[u].push_back(v); //有向图
}
for (int i = 1; i <= n; i++){
fill(vis, vis + MAXV, false);
dfs(i, i);
}
int cnt = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (c[i][j] && c[j][i])
cnt++;
cout << (cnt - n) / 2;
return 0;
}
任何一对相互可达的结点对一定在同一个强连通分量之中,这是显然的。因此算法只需要划分强连通分量,再利用一点点排列组合的知识,就可以得到结果。
这个算法成功拿到100分,而且所用的时间远远低于限制的时间。毕竟tarjan算法求连通分量是线性复杂度呀。
tarjan算法参考的是李煜东的《算法竞赛进阶指南》,是一本超级棒的书。
#include
#include
#include
using namespace std;
//使用tarjan算法轻松解决这个问题。tarjan写起来并不难,只是要说出它的正确性很不简单,我说不出。。
const int MAXN = 10010;
int n, m, top, cnt, t; //默认值都为0
int low[MAXN], pre[MAXN], stk[MAXN], c[MAXN], id[MAXN];
bool ins[MAXN];
vector<vector<int> > G(MAXN);
void tarjan(int u){
low[u] = pre[u] = ++t;
stk[++top] = u; ins[u] = true;
for (auto v : G[u]){
if (!pre[v]){
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (ins[v])
low[u] = min(low[u], pre[v]);
}
if (low[u] == pre[u]){
cnt++;
int w;
do{
w = stk[top--]; ins[w] = false;
id[w] = cnt;
c[cnt]++;
} while (w != u);
}
}
int main(){
cin >> n >>m;
int u, v;
for (int i = 0; i < m; i++){
cin >> u >> v;
G[u].push_back(v);
}
for (int i = 1; i <= n; i++)
if (!pre[i])
tarjan(i);
int ans = 0;
for (int i = 1; i <= cnt; i++)
if (c[i] > 1)
ans += (c[i] * (c[i] - 1) / 2);
cout << ans;
return 0;
}