并查集练习题以及带权并查集简介
1.并查集模板(luogu——P3367)
1.问题描述:
题目链接
2.分析:
这道题目就是一道普通的并查集模板题目,只要对并查集的初始化,查找,合并有所了解或者看到上一篇介绍并查集算法的文章,直接敲即可,这里不过多赘述。
3.AC_Code:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 5;
int father[maxn];
void init(int n) {
for (int i = 1; i <= n; i++) {
father[i] = i;
}
}
int find(int x) {
if (x == father[x]) return x;
return father[x] = find(father[x]);
}
void merge(int a, int b) {
int fa = find(a);
int fb = find(b);
if (fa != fb) {
father[fa] = fb;
}
}
int main() {
int n, m, op, x, y;
scanf("%d%d", &n, &m);
init(n);
while (m --) {
scanf("%d%d%d", &op, &x, &y);
if (op == 1) {
merge(x, y);
} else if (op == 2) {
if (find(x) == find(y)) printf("Y\n");
else printf("N\n");
}
}
return 0;
}
2.朋友(luogu——P2078)
1.问题描述:
题目链接
2.分析:
这道题目其实是查A公司里和B公司里与1认识的男生数目和与-1认识的女生数目,求出来后我们得到的最小值就是最多能配的情侣。需要注意的是1和-1本身也算。
对于样例:
我们可以做两个并查集,1表示A公司,2表示B公司,然后我们查询A公司的1有多少个认识的朋友,包括自己,B公司的1(相当于-1),有多少认识的朋友,包括自己。
对于样例A公司的1认识的人(包括自己)3个,B公司的1认识的人(包括自己)2个
二者中取交集也就是能得到2,即取最小值。
3.AC_Code:
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5;
int father1[maxn], father2[maxn];
void init1(int n) {
for (int i = 1; i <= n; i++)
father1[i] = i;
}
void init2(int m) {
for (int i = 1; i <= m; i++)
father2[i] = i;
}
int get1(int x) {
if (x == father1[x]) return x;
return father1[x] = get1(father1[x]);
}
int get2(int x) {
if (x == father2[x]) return x;
return father2[x] = get2(father2[x]);
}
void merge1(int a, int b) {
int fa = get1(a);
int fb = get1(b);
if (fa != fb) {
father1[fa] = fb;
}
}
void merge2(int a, int b) {
int fa = get2(a);
int fb = get2(b);
if (fa != fb) {
father2[fa] = fb;
}
}
int main() {
int n, m, p, q, x, y;
scanf("%d%d%d%d", &n, &m, &p, &q);
init1(n);
init2(m);
while (p --) {
scanf("%d%d", &x, &y);
merge1(x, y);
}
while (q --) {
scanf("%d%d", &x, &y);
merge2(-x, -y);
}
int cnt1 = 0, cnt2 = 0;
for (int i = 1; i <= n; i++) {
if (get1(i) == get1(1)) cnt1++;
}
for (int i = 1; i <= m; i++) {
if (get2(i) == get2(1)) cnt2++;
}
printf("%d\n", min(cnt1, cnt2));
return 0;
}
3.带权并查集简介:
4.带权并查集——NOI2002银河英雄传说:
2.分析:
这是一道很经典的带权并查集。
我们可以知道一共有两种操作,一种是合并,还有一种是查询。
但是和模板不一样了,我们的查询操作需要查x与y之间的元素有多少元素,我们需要增加两个数组了,一个是dis数组用来表示这个点到所在的队列的队头的距离,另一个是size数组用来表示这个队列的长度,如果我们要求x和y之间有多少元素就求出dis[x] - dis[y] - 1为什么要剪掉1呢,因为dis数组表示这个战舰之前不包括这个战舰本身到队头的元素。所以需要交掉dis[x]本身。
我们需要在压缩路径的时候,维护dis数组,在合并的时候维护size数组以及dis数组。
查找:
dis[x]记录战舰x与father[x]之间的边的权值。在路径压缩把x直接指向树根的同时,我们把dis[x]更新从x指向树根的同时,我们把dis[x]更新为从x到树根的路径上的所有边权之和。
合并:
合并的时候因为要将某一个队列A的队头指向另一个队列B,所以dis数组需要加上另一个队列B的长度,然后我们在更新队列B长度。
3.AC_Code:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e4 + 5;
int father[maxn], dis[maxn], size[maxn];
void init() {
for (int i = 1; i <= maxn; i++) {
father[i] = i;
dis[i] = 0;
size[i] = 1;
}
}
int find(int x) {
if (x == father[x]) return x;
int f = find(father[x]);
dis[x] += dis[father[x]];
return father[x] = f;
}
void merge(int a, int b) {
int fa = find(a);
int fb = find(b);
father[fa] = fb;
dis[fa] += size[fb];
size[fb] += size[fa];
}
int main() {
int T, x, y;
char op;
scanf("%d", &T);
init();
while (T --) {
scanf(" %c%d%d", &op, &x, &y);
if (op == 'M') {
merge(x, y);
} else {
if (find(x) != find(y)) {
printf("-1\n");
} else {
printf("%d\n", (int)abs(dis[x] - dis[y]) - 1);
}
}
}
return 0;
}
欢迎关注:
ly’s Blog