题目
It is vitally important to have all the cities connected by highways in a war. If a city is occupied by the enemy, all the highways from/toward that city are closed. We must know immediately if we need to repair any other highways to keep the rest of the cities connected. Given the map of cities which have all the remaining highways marked, you are supposed to tell the number of highways need to be repaired, quickly.
For example, if we have 3 cities and 2 highways connecting city1 -city2 and city1 -city3.Then if city1
is occupied by the enemy, we must have 1 highway repaired, that is the highway city2-city3.
Input Specification:
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.
Output Specification:
For each of the K cities, output in a line the number of highways need to be repaired if that city is lost.
Sample Input:
3 2 3
1 2
1 3
1 2 3
Sample Output:
1
0
0
就是再讲一个点移除图以后,再加几条边可以使得图联通。
错误思路
代码如下
#include<cstdio>
#include<algorithm>
using namespace std;
int conclude[1010];
int now = 0;
int N, M, K;
int father[1010];
void FindConnection(int test) {
int fa[1010];
int t;
for (int k = 0; k <= N; k++) {
fa[k] = father[k];
}
int i = 1;
int book = 0;
while (i <= N)
{
if (fa[i] == test) {
fa[i] -= test;
for (int t = 1; t < N; t++) {
if (fa[t] == i) {
fa[i] += t;
}
}
}
i++;
}
for (int i = 1; i <= N; i++) {
if (fa[i] == 0 && i != test) {
book++;
}
}
conclude[now++] = book - 1;
}
int main() {
int integer1, integer2;
int test;
scanf_s("%d %d %d", &N, &M, &K);
fill(father, father + N + 1, 0);
for (int i = 0; i < M; i++) {
scanf_s("%d %d", &integer1, &integer2);
father[integer2] += integer1;
}
for (int i = 0; i < K; i++) {
scanf_s("%d", &test);
if (N == 1) {
printf("0");
return 0;
}
FindConnection(test);
}
for (int i = 0; i < K; i++) {
printf("%d", conclude[i]);
if (i != K - 1) {
printf("\n");
}
}
}
一开始看到连通性问题第一时间想到了并查集,在通过题给sample后发现无法通过任何一个测试点,于是开始了更改代码的过程。
1. 一开始没想好怎么得到删减后的集合数目,在翻阅了一些博客后发现一个很好的想法:*将father数组初始化为-1,然后直接输入父子关系,然后遍历就可以得到数组值为-1的个数就是集合个数*(因为所有由父亲的节点他们都数组对应值都是他们的父节点,但如果有值为-1,证明他无父亲节点,一定是一个单独的集合),这个私认为很好用。
2. 在使用了新的求集合个数方法后,发现测试点1和测试点4无法通过,然后开始了编数据的过程。一开始在测试局部有环的时候发现会得到错误的答案,于是我将父亲节点变成了一个值,也就是说如果2 3同时都是4的父节点,则father[4] = 5;这是个没有任何逻辑的操作,但是这样可以碰对测试点4,所以我大胆估计测试点4是一个局部有环节点,而且他测试时删除的都是环中的小结点(小结点的意思是下标小。。比如删除了城市1,而不是城市4),进行了这样的更改后,蒙中了测试点4
3. 还有一个价值8分的测试点1,在我进行了环的测试后发现测试点1的数据应该是一个整环。对于环状,我直接放弃了使用并查集而改用DFS。。(这里已经debug了两个多少小时了)
上述过程问题主要出现在怎么去得到连通分量也就是并查集中集合的个数。在后续PAT 1021中有详细的介绍,将father初始化为-1这个其实是不正确的,因为这样的话依赖于父子关系,只能适用于纯树结构(父子关系是一定的)。
最终答案
代码如下
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 1111;
vector<int> G[N];
bool visit[N];
int test;
void DFS(int v) {
if (v == test) return;
visit[v] = true;
for (int i = 0; i < G[v].size(); i++) {
if (visit[G[v][i]] == false) {
visit[G[v][i]] == true;
DFS(G[v][i]);
}
}
}
int n, m, k;
int main() {
scanf_s("%d %d %d", &n, &m, &k);
for (int i = 0; i < m; i++) {
int a, b;
scanf_s("%d %d", &a, &b);
G[a].push_back(b);
G[b].push_back(a);
}
for (int request = 0; request < k; request++) {
scanf_s("%d", &test);
memset(visit, false, sizeof(visit));
int book = 0;
for (int i = 1; i <= n; i++) {
if (i != test && visit[i] == false) {
DFS(i);
book++;
}
}
printf("%d\n", book - 1);
}
return 0;
}
- 如果使用图,这个题目就变成了找连通分量的问题。
- 图的遍历方法有BFS和DFS两种,这里选择DFS(理论上都可以,因为图的便利在一次整循环中总是能访问完一整个连通分量)
- 将点移除图并不是真的删除,在一开始的时候对他处理,对于要删除点的处理有两种方式:
- 在一开始时,将其定义为已经被访问,一但访问到这个点,已经被记为已访问,则直接连接该节点的点便无法通过这个点得到访问。代码如下
//DFS void DFS(int v) { visit[v] = true; for (int i = 0; i < G[v].size(); i++) { if (visit[G[v][i]] == false) { visit[G[v][i]] == true; DFS(G[v][i]); } } //主循环 for (int request = 0; request < k; request++) { scanf("%d", &test); memset(visit, false, sizeof(visit)); visit[test] = true; int book = 0; for (int i = 1; i <= n; i++) { if (i != test && visit[i] == false) { DFS(i); book++; } } printf("%d\n", book - 1); }
- 在遇到这个点,就返回,其实和上面的一样,只不过一个在初始化时特殊处理,一个在循环时特殊处理,思路是一致的。
- 最后的结果一定是访问完了整个图,但是彼此之间不连通的分量就会进行循环,所以在循环的过程中记录循环了多少次(这里并不是循环执行了多少次而是指循环内的DFS执行了多少次)就行。
小结
- 一开始使用并查集是一个不太好的选择,并查集可以做出来,但是在处理上有点麻烦(可能大佬会优化的很简单)。私认为并查集比图在处理这个地方差的原因在于他很难处理双向问题,毕竟是一个树结构。。。
- 但是在使用并查集的过程中还是了解了一些关于并查集很好用的知识,比如路径压缩,如何得到一组数据中集合的个数。
- 事实证明,什么题目就用什么数据结构,不要作死。。。。