Description:
关系R具有对称性和传递性。数对p q表示pRq,p和q是0或自然数,p不等于q。
要求写一个程序将数对序列进行过滤,如果一个数对可以通过前面数对的传递性得到,则将其滤去。例如:
输入 输出 连通性
3 4— 3 4
4 9— 4 9
8 0— 8 0
2 3— 2 3
5 6— 5 6
2 9— XX— 2-3-4-9
5 9— 5 9
7 3— 7 3
4 8— 4 8
5 6— XX— 5-6
0 2— XX— 0-8-4-3-2
6 1— 6 1其中数对2 9和0 2可由之前数对的连通关系得到,故不做输出。
Input
输入共有m行(0<=m<=1000000),每行一个数对,数对的数字之间以1个空格分隔;数对的数字为0或n=100000以内的自然数。Output
输出包含过滤之后的数对序列。每行输出一个数对,数对的数字之间以1个空格分隔。Sample Input
3 4
4 9
8 0
2 3
5 6
2 9
5 9
7 3
4 8
5 6
0 2
6 1
Sample Output
3 4
4 9
8 0
2 3
5 6
5 9
7 3
4 8
6 1
看完题目,第一反应就是动态构造图,然后每次输入时进行连通性检查。但是仔细一看,发现根本行不通。
如题目所示,节点数可能达到100000,构造一个邻接矩阵即使是bool类型也显然超出内存限制,同时输入数据可能达到1000000,每一次都进行BFS或DFS连通性检测显然会严重超时。
那么只有走其他方法!
对于连通性检测,如已知图2-3-4-5,显然2-3,2-4,2-5,3-4,3-5,4-5都是需要过滤的,不难发现:2是3,4,5的共同祖先,或者反过来,5是2,3,4的共同祖先。由此可以联想到Union-find,对结点进行分组。
我采用的第一种方法代码如下:
# include <iostream>
# define MAX 100001
using namespace std;
int data[MAX];
int main(void) {
for (int i = 0; i < MAX; i++) {
data[i] = i;
}
int point1, point2;
while(cin >> point1 >> point2) {
int p = data[point1];
int q = data[point2];
if (data[point1] != data[point2]) {
/*cout << "point1" << data[point1] << endl;
cout << "point2" << data[point2] << endl;*/
cout << point1 << " " << point2 << endl;
for (int i = 0; i < MAX; i++) {
if (data[i] == p) {
data[i] = q;
}
}
}
}
return 0;
}
起初每个节点都属于自己的组,我就暂且将它们各自的编号定义为它们的index。
当要查询的两个index在数组中的值不同时,说明它们不属于同一个组,应当连通;否则,说明它们已经连通,需要过滤掉。
但有一点要注意,在连通时,应当把两个组内所有成员都进行连通,但是我们又不知道哪些需要连通,只能把数组遍历一遍进行连通。
但是提交后发现代码超时了!
接下来就是改进我的代码了。
很显然就是在连通时遍历数组耗时太长了,每一次连通都把100001个数据遍历一次,实在是资源浪费,因为有时并没有那么多结点。
所以我改进了代码,如下:
# include <iostream>
# define MAX 100001
using namespace std;
int data[MAX];
int _max = 0;
int main(void) {
for (int i = 0; i < MAX; i++) {
data[i] = i;
}
int point1, point2;
while(cin >> point1 >> point2) {
if (point1 > _max) _max = point1;
if (point2 > _max) _max = point2;
int p = data[point1];
int q = data[point2];
if (data[point1] != data[point2]) {
cout << point1 << " " << point2 << endl;
for (int i = 0; i <= _max; i++) {
if (data[i] == p) {
data[i] = q;
}
}
}
}
return 0;
}
我用_max变量存储最大结点的值,这样一来就不用将所有数据遍历。
但是一提交,还是超时了!我估计测试数据里可能一早就用到了一个很大很大的结点。所以我改进后的代码还是废柴。
那么,还是需要改进!!!
问题是:怎么才能避免遍历数组?因为不知道哪些结点需要进行连通,才导致我们需要遍历整个数组。那么我们怎样才能找到这些结点而不需要遍历数组呢?
肯定要用到某种数据结构。链表?集合?更深一层想,查找最高效的就是树了。
使用树,我便能层层回溯,找到根结点,也就是它最终所属的组,这样我就可以避免遍历了!
于是我进一步改进了我的代码:
# include <iostream>
# define MAX 100001
using namespace std;
int data[MAX];
int _max = 0;
int find(int num) {
return (num == data[num] ? num : find(data[num]));
}
int main(void) {
for (int i = 0; i < MAX; i++) {
data[i] = i;
}
int point1, point2;
while(cin >> point1 >> point2) {
int p = find(point1);
int q = find(point2);
if (p != q) {
cout << point1 << " " << point2 << endl;
data[p] = data[q];
}
}
return 0;
}
本想着这次肯定万无一失能够通过,还是太嫩了!依然超时!
为什么会这样?说明树太深了,导致每次查找根结点的时候耗费的资源太多了!我们必须在查找的过程中将树简化。
于是我就修改了find函数,代码如下:
# include <iostream>
# define MAX 100001
using namespace std;
int data[MAX];
int _max = 0;
int find(int num) {
return (num == data[num] ? num : data[num] = find(data[num])); // 此处为修改的地方
}
int main(void) {
for (int i = 0; i < MAX; i++) {
data[i] = i;
}
int point1, point2;
while(cin >> point1 >> point2) {
int p = find(point1);
int q = find(point2);
if (p != q) {
cout << point1 << " " << point2 << endl;
data[p] = data[q];
}
}
return 0;
}
然后,终于过了,哈哈哈~真是有趣的一道题!
以上内容皆为本人观点,欢迎大家提出批评和指导,我们一起探讨!