并查集练习题以及带权并查集简介

1.并查集模板(luogu——P3367)

1.问题描述:
题目链接

2.分析:
这道题目就是一道普通的并查集模板题目,只要对并查集的初始化,查找,合并有所了解或者看到上一篇介绍并查集算法的文章,直接敲即可,这里不过多赘述。

3.AC_Code:

#include 
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张图片
我们可以做两个并查集,1表示A公司,2表示B公司,然后我们查询A公司的1有多少个认识的朋友,包括自己,B公司的1(相当于-1),有多少认识的朋友,包括自己。
对于样例A公司的1认识的人(包括自己)3个,B公司的1认识的人(包括自己)2个
二者中取交集也就是能得到2,即取最小值。

3.AC_Code:

#include 
#include 
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.带权并查集简介:

并查集练习题以及带权并查集简介_第2张图片
并查集练习题以及带权并查集简介_第3张图片
并查集练习题以及带权并查集简介_第4张图片
并查集练习题以及带权并查集简介_第5张图片

4.带权并查集——NOI2002银河英雄传说:

1.题目链接:luogu——P1196

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 
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

你可能感兴趣的:(并查集,Oj,C++,数据结构)