第一道Java不可能不超时的题目
给定一个图,图上的顶点代表城市,图上的边代表城市之间的道路。如果一个城市被敌人攻占,那么这个城市通往其他城市的道路都会关闭。
问:当一个城市被敌人攻占后,需要至少修多少条路,才能让其他城市重新连通起来?
(意思是:给定一个图(默认应该是连通的),删除指定一个顶点(以及和它相邻的所有边),需要至少再添加多少条边,才能让图重新连通起来?)
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还是并查集都是要访问了几乎每个顶点每条边。如果这个题目改成逐个添加顶点而不是删去,相信并查集的效率会大大提高。