题目描述
Mr Wang wants some boys to help him with a project. Because the project is rather complex, the more boys come, the better it will be. Of course there are certain requirements.Mr Wang selected a room big enough to hold the boys. The boy who are not been chosen has to leave the room immediately. There are 10000000 boys in the room numbered from 1 to 10000000 at the very beginning. After Mr Wang’s selection any two of them who are still in this room should be friends (direct or indirect), or there is only one boy left. Given all the direct friend-pairs, you should decide the best way.
输入
The first line of the input contains an integer n (0 ≤ n ≤ 100 000) - the number of direct friend-pairs. The following n lines each contains a pair of numbers A and B separated by a single space that suggests A and B are direct friends. (A ≠ B, 1 ≤ A, B ≤ 10000000)
输出
The output in one line contains exactly one integer equals to the maximum number of boys Mr Wang may keep.
样例输入
3
1 3
1 5
2 5
4
3 2
3 4
1 6
2 6
样例输出
4
5
注意:以下所有代码中的m和题目中的n含义相同,表示朋友关系的对数。这是为了和《算法笔记》保持一致。
思路一:题目等价于求最大集合的元素个数。所以可以直接将每个集合的元素个数保存在根节点即可。这样做的缺点是由于题目给定的数量级较大,会花费大量的时间和内存空间。
#include <cstdio>
#include <cstring>
const int n = 10000010;
int father[n],isRoot[n];
int findFather(int x) { //寻找x所在集合的根结点
int a = x;
while (x != father[x]) {
x = father[x];
}
//路径压缩
while (a != father[a]) {
int z = a;
a = father[a];
father[z] = x;
}
return x;
}
void Union(int a, int b) { //合并a和b所在集合
int fA = findFather(a);
int fB = findFather(b);
if (fA != fB) {
father[fA] = fB;
}
}
int main() {
int m, a, b;
while (scanf("%d", &m) != EOF) {
if (m == 0) {
printf("1\n");
continue;
}
memset(isRoot, 0, sizeof(isRoot));
for (int i = 1; i < n; i++) father[i] = i;
for (int i = 0; i < m; i++) {
scanf("%d %d", &a, &b);
Union(a, b);
}
for (int i = 1; i <= n; i++) {
//将集合元素个数保存在根节点
isRoot[findFather(i)]++;
}
int max = -1;
for (int i = 1; i <= n; i++) {
if (max < isRoot[i]) {
max = isRoot[i];
}
}
printf("%d\n", max);
}
return 0;
}
思路二:在合并两个集合时,更新当前最大集合的元素数量。初始化的时候需要考虑是否之前已经初始化过(已经初始化过就不能再初始化)。注意记录元素是否已经初始化的数组用bool类型会比用int快很多,且花费内存也很小很多。
#include <cstdio>
#include <cstring>
const int n = 10000010;
int father[n], num[n]; // num记录集合元素数量
bool cnt[n]; //是否已经初始化
int maxnum = -1; //记录最大集合元素数量
int findFather(int a) {
int x = a;
while (x != father[x]) {
x = father[x];
}
while (a != father[a]) {
int z = a;
a = father[a];
father[z] = x;
}
return x;
}
void Union(int a, int b) {
int fA = findFather(a);
int fB = findFather(b);
if (fA != fB) {
father[fA] = fB;
num[fB] += num[fA]; //更新合并后的集合元素数量
if (maxnum < num[fB]) {
maxnum = num[fB];
}
}
}
void init(int n) { //初始化集合
if (!cnt[n]) { //若是第一次出现就初始化
father[n] = n;
num[n] = 1;
cnt[n] = true;
} else {
return;
}
}
int main() {
int m, a, b;
while (scanf("%d", &m) != EOF) {
if (m == 0) {
printf("1\n");
continue;
}
memset(cnt, 0, sizeof(cnt));
maxnum = 0;
for (int i = 0; i < m; i++) {
scanf("%d %d", &a, &b);
//初始化集合a和b
init(a);
init(b);
Union(a, b);
}
printf("%d\n", maxnum);
}
return 0;
}
思路三:在思路二的基础上,先用一个vector记录所有的朋友关系,最后再合并。相比思路二,初始化的时候可以不用考虑是否是第一次出现,因为最后合并的时候使用的是vector内记录的元素关系副本。时间比思路二更快,内存也消耗更少。
#include <cstdio>
#include <vector>
#include <map>
using namespace std;
const int n = 10000010;
int father[n], num[n]; // num记录集合元素数量
int maxnum = -1; //记录最大集合元素数量
int findFather(int a) {
int x = a;
while (x != father[x]) {
x = father[x];
}
while (a != father[a]) {
int z = a;
a = father[a];
father[z] = x;
}
return x;
}
void Union(int a, int b) {
int fA = findFather(a);
int fB = findFather(b);
if (fA != fB) {
father[fA] = fB;
num[fB] += num[fA]; //更新合并后的集合元素数量
if (maxnum < num[fB]) {
maxnum = num[fB];
}
}
}
void init(int n) { //初始化集合
father[n] = n;
num[n] = 1;
}
int main() {
int m, a, b;
vector<pair<int, int>> v;
while (scanf("%d", &m) != EOF) {
if (m == 0) {
printf("1\n");
continue;
}
p.clear();
for (int i = 0; i < m; i++) {
scanf("%d %d", &a, &b);
//初始化集合a和b
init(a);
init(b);
v.push_back(make_pair(a, b));
}
maxnum = 0;
for (int i = 0; i < p.size(); i++) {
Union(v[i].first, v[i].second);
}
printf("%d\n", maxnum);
}
return 0;
}