第十章 并查集
AcWing 1485. 战争中的城市
问题描述
-
问题链接:AcWing 1485. 战争中的城市、原题链接
分析
- 本题的思路是对于每个去掉的城市,求一遍连通分量的个数,最终需要修建的最少高速公路的数目就是连通分量个数减一的结果。
代码
- C++
#include <iostream>
using namespace std;
const int N = 1010, M = 500010;
int n, m, k; // 点数、边数、查询数
int p[N];
struct Edge {
int a, b;
} e[M];
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
cin >> n >> m >> k;
for (int i = 0; i < m; i++) scanf("%d%d", &e[i].a, &e[i].b);
while (k--) {
int x;
scanf("%d", &x);
for (int i = 1; i <= n; i++) p[i] = i;
int cnt = n - 1;
for (int i = 0; i < m; i++) {
int a = e[i].a, b = e[i].b;
if (a != x && b != x) {
int pa = find(a), pb = find(b);
if (pa != pb) {
p[pa] = pb;
cnt--;
}
}
}
printf("%d\n", cnt - 1);
}
return 0;
}
AcWing 1604. 家产
问题描述
-
问题链接:AcWing 1604. 家产、原题链接
分析
- 首先读入所有边的信息,然后合并在一个连通块中的点。注意因为题目输出时要求输出整个家族中最小的编号,在合并的时候,根节点值较大的合并到值较小的即可。
代码
- C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 10010;
int n;
int p[N], c[N], hc[N], ha[N]; // 并查集、家庭人数、房子数,房子面积
bool st[N]; // 记录该编号是否存在
struct Edge {
int a, b;
} e[N];
struct Family {
int id, c, hc, ha;
bool operator< (const Family& t) const {
// ha / c ? t.ha / t.c
if (ha * t.c != t.ha * c) return ha * t.c > t.ha * c;
return id < t.id;
}
};
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
cin >> n;
int m = 0;
for (int i = 0; i < n; i++) {
int id, father, mother, k;
cin >> id >> father >> mother >> k;
st[id] = true;
if (father != -1) e[m++] = {id, father};
if (mother != -1) e[m++] = {id, mother};
for (int i = 0; i < k; i++) {
int son;
cin >> son;
e[m++] = {id, son};
}
cin >> hc[id] >> ha[id];
}
for (int i = 0; i < N; i++) p[i] = i, c[i] = 1;
for (int i = 0; i < m; i++ ) {
int a = e[i].a, b = e[i].b;
st[a] = st[b] = true;
int pa = find(a), pb = find(b);
if (pa != pb) {
if (pa < pb) swap(pa, pb);
c[pb] += c[pa];
hc[pb] += hc[pa];
ha[pb] += ha[pa];
p[pa] = pb;
}
}
vector<Family> family;
for (int i = 0; i < N; i++)
if (st[i] && p[i] == i)
family.push_back({i, c[i], hc[i], ha[i]});
sort(family.begin(), family.end());
cout << family.size() << endl;
for (auto f : family)
printf("%04d %d %.3lf %.3lf\n", f.id, f.c, (double) f.hc / f.c, (double) f.ha / f.c);
return 0;
}
AcWing 1608. 森林里的鸟
问题描述
-
问题链接:AcWing 1608. 森林里的鸟、原题链接
分析
-
假设鸟的数量为
tot
,初始每个鸟在一个集合中,使用cnt
记录合并的次数,因为每合并一次,集合的数量就减少一个,因此最终集合的数量为tot-cnt
,也就是树的数量。 -
后面查询两个鸟是否在同一个集合中,直接查询即可。
代码
- C++
#include <iostream>
using namespace std;
const int N = 10010;
int n;
int birds[15]; // 存储每张图片中的鸟的编号
int p[N];
bool st[N]; // 记录出现的鸟
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
scanf("%d", &n);
for (int i = 0; i < N; i++) p[i] = i;
int cnt = 0; // 记录合并的次数
for (int i = 0; i < n; i++) {
int k;
scanf("%d", &k);
for (int j = 0; j < k; j++) {
scanf("%d", &birds[j]);
st[birds[j]] = true;
}
for (int j = 1; j < k; j++) {
int a = birds[j - 1], b = birds[j];
a = find(a), b = find(b);
if (a != b) {
p[a] = b;
cnt++;
}
}
}
int tot = 0;
for (int i = 1; i < N; i++) tot += st[i];
printf("%d %d\n", tot - cnt, tot);
int q;
scanf("%d", &q);
while (q--) {
int a, b;
scanf("%d%d", &a, &b);
if (find(a) == find(b)) puts("Yes");
else puts("No");
}
return 0;
}
AcWing 1597. 社会集群
问题描述
-
问题链接:AcWing 1597. 社会集群、原题链接
分析
-
我们使用对于每个爱好建立一个
vector
,这样所有有这个爱好的人会在同一个vector
中,之后合并每个vector
中的人即可。 -
使用
cnt
数组记录每个集合中元素的数量,最后排序输出即可。
代码
- C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
vector<int> hobby[N];
int p[N];
int cnt[N]; // 每个集合中元素个数
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int k;
scanf("%d:", &k);
while (k--) {
int h;
scanf("%d", &h);
hobby[h].push_back(i);
}
}
for (int i = 1; i <= n; i++) p[i] = i;
for (int i = 1; i <= 1000; i++) // 遍历1000个爱好
for (int j = 1; j < hobby[i].size(); j++) {
int a = hobby[i][0], b = hobby[i][j]; // (a, b)有相同的爱好i
p[find(a)] = find(b);
}
for (int i = 1; i <= n; i++) cnt[find(i)]++;
sort(cnt, cnt + N, greater<int>());
int k = 0;
while (cnt[k]) k++;
printf("%d\n", k);
printf("%d", cnt[0]);
for (int i = 1; i < k; i++)
printf(" %d", cnt[i]);
puts("");
return 0;
}
AcWing 836. 合并集合
问题描述
-
问题链接:AcWing 836. 合并集合
分析
- 并查集模板题。关于并查集的讲解可以参考:并查集。
代码
- C++
#include <iostream>
using namespace std;
const int N = 100010;
int p[N];
int find(int x) {
if (p[x] != x) p[x] = find(p[x]); // 路径压缩
return p[x];
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) p[i] = i; // 并查集初始化
while (m--) {
char op[2];
int a, b;
scanf("%s%d%d", op, &a, &b);
if (*op == 'M') p[find(a)] = find(b);
else {
if (find(a) == find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}