ccf认证 201509-4 (floyd,dfs,tarjan三种算法)

文章目录

  • ccf认证 201509-4 高速公路 (floyd,dfs,tarjan三种算法)
    • 审题
    • floyd算法求传递闭包
    • dfs求传递闭包
    • tarjan算法直接求强连通分量

ccf认证 201509-4 高速公路 (floyd,dfs,tarjan三种算法)

审题

这道题目很显然和传递闭包有关,要求出有多少个便利城市对,即求出图中相互可达的结点对的数量,如果我们有传递闭包,则可以迅速求出来。求传递闭包的算法有不少,包括floyd算法、dfs算法等。

然而传递闭包算法的效率不够高、不足以满分通过。再分析题目发现,其实并不需要求出传递闭包,而只需要进行强连通分量的划分即可。使用tarjan算法可以达到线性复杂度,是这道题目的最好解决方法。

下面这几种算法都将尝试一下,还可以对比它们的效率。

floyd算法求传递闭包

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;
}

dfs求传递闭包

众所周知,从一个点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;
}

tarjan算法直接求强连通分量

任何一对相互可达的结点对一定在同一个强连通分量之中,这是显然的。因此算法只需要划分强连通分量,再利用一点点排列组合的知识,就可以得到结果。

这个算法成功拿到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;
}

你可能感兴趣的:(ccf认证,tarjan算法,算法,算法,c++,ccf)