PAT 甲级 1013 Battle Over Cities

题目描述

第一道Java不可能不超时的题目
PAT 甲级 1013 Battle Over Cities_第1张图片
给定一个图,图上的顶点代表城市,图上的边代表城市之间的道路。如果一个城市被敌人攻占,那么这个城市通往其他城市的道路都会关闭。
问:当一个城市被敌人攻占后,需要至少修多少条路,才能让其他城市重新连通起来?
(意思是:给定一个图(默认应该是连通的),删除指定一个顶点(以及和它相邻的所有边),需要至少再添加多少条边,才能让图重新连通起来?)

输入描述

Each input file contains one test case. Each case starts with a line containing 3 numbers N (<1000), M and K, which are the total number of cities, the number of remaining highways, and the number of cities to be checked, respectively. Then M lines follow, each describes a highway by 2 integers, which are the numbers of the cities the highway connects. The cities are numbered from 1 to N. Finally there is a line containing K numbers, which represent the cities we concern.

输出描述

For each of the K cities, output in a line the number of highways need to be repaired if that city is lost.

输入样例

3 2 3
1 2
1 3
1 2 3

输出样例

3 2 3
1 2
1 3
1 2 3

题目分析

如果数据结构的图论一块知识了解过的话,很容看出,这就是求连通分量的问题。如果一个图的连通分量数为n,则让这个图成为一个连通图则至少需要添加n-1条边。这个题目稍微复杂了一点,他的原图本身是连通的,给定K个查询,每个查询都会从原图中删去一个顶点,然后再求连通分量n,输出n-1即可。
求连通分量有两种方法:DFS和并查集(不相交集)
DFS:

#include 
#include 
using namespace std;
int N, M, K;
vector<vector<int> > adj;
bool known[1010];
void dfs(int id) {
	known[id] = true;
	for (int e : adj[id]) {
		if (!known[e])
			dfs(e);
	}
}
int main() {
	scanf("%d %d %d", &N, &M, &K);
	adj.resize(N + 1);
	for (int i = 0; i < M; i++) {
		int one, ano;
		scanf("%d %d", &one, &ano);
		adj[one].push_back(ano);
		adj[ano].push_back(one);
	}
	for (int i = 0; i < K; i++) {
		fill(known, known + N + 1, false);
		int q;
		scanf("%d", &q);
		known[q] = true;
		int cnt = 0;
		for (int j = 1; j <= N; j++) {
			if (!known[j]) {
				cnt++;
				dfs(j);
			}
		}
		printf("%d\n", cnt - 1);
	}
	return 0;
}

DFS方法中,求连通分量就是从1到N遍历每个顶点,每次遇到未访问的顶点就代表找到了新的连通分支,然后用dfs把当前连通分支的全部顶点访问完(如果整个图是连通图,那意味着从顶点1开始DFS就可以访问到所有的顶点)。所谓删去一个顶点,就是在遍历之前就将一个顶点访问但是不从该点DFS,这样被删去的顶点所邻接的边在后续遍历时就不会被使用到。

并查集:

#include 
#include 
using namespace std;
int N, M, K;
vector<vector<int> > adj;
vector<int> f, rankv;
int findSet(int x) {
	if (x != f[x])
		f[x] = findSet(f[x]);
	return f[x];
}
void join(int x, int y) {
	int fx = findSet(x), fy = findSet(y);
	if (rankv[fx] > rankv[fy])
		f[fy] = fx;
	else {
		f[fx] = fy;
		if (rankv[fx] == rankv[fy])
			rankv[fy]++;
	}
}
void getCnt(int q,int& cnt) {
	for (int i = 1; i <= N; i++) f[i] = i;
	for (int i = 1; i <= N; i++) {
		if (i != q) {
			for (int j = 0; j < adj[i].size(); j++) {
				if (adj[i][j] != q)
					join(i, adj[i][j]);
			}
		}
	}
	for (int i = 1; i <= N; i++) {
		if (i != q && i == f[i]) 
			cnt++;
	}
}
int main() {
	scanf("%d %d %d", &N, &M, &K);
	adj.resize(N + 1);
	f.resize(N + 1);
	rankv.resize(N + 1, 0);
	for (int i = 0; i < M; i++) {
		int one, ano;
		scanf("%d %d", &one, &ano);
		adj[one].push_back(ano);
		adj[ano].push_back(one);
	}
	for (int i = 0; i < K; i++) {
		int cnt = 0, q;
		scanf("%d", &q);
		getCnt(q, cnt);
		printf("%d\n", cnt - 1);
	}
	return 0;
}

并查集是求连通分量、判断两个顶点是否可达的经典方法,这里采用了带路径压缩的按秩合并的并查集(实际上这个题目只要做到路径压缩就不会超时)。实际上这道题并查集并没有比DFS快,原因在于不管是DFS还是并查集都是要访问了几乎每个顶点每条边。如果这个题目改成逐个添加顶点而不是删去,相信并查集的效率会大大提高。

你可能感兴趣的:(PAT甲级,数据结构,图论,c++)