2021-01-21

本文深入探讨了并查集这一数据结构,通过实例解析其在解决朋友聚会分桌问题中的应用。并查集可以高效地处理集合的合并与查找操作,避免陌生人坐在一起。同时,文章还介绍了如何利用并查集解决复杂条件下的问题,如动物王国的食物链问题,涉及多关系的集合判断。通过开多倍并查集,有效地处理了各种条件的冲突和判断,展示了并查集在实际问题中的灵活性和实用性。
摘要由CSDN通过智能技术生成

有关于并查集入门的知识点整理

先放个简单的类题
Today is Ignatius’ birthday. He invites a lot of friends. Now it’s dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers.

One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table.

For example: If I tell you A knows B, B knows C, and D knows E, so A, B, C can stay in one table, and D, E have to stay in the other one. So Ignatius needs 2 tables at least.
Input
The input starts with an integer T(1<=T<=25) which indicate the number of test cases. Then T test cases follow. Each test case starts with two integers N and M(1<=N,M<=1000). N indicates the number of friends, the friends are marked from 1 to N. Then M lines follow. Each line consists of two integers A and B(A!=B), that means friend A and friend B know each other. There will be a blank line between two cases.
Output
For each test case, just output how many tables Ignatius needs at least. Do NOT print any blanks.
Sample Input
2
5 3
1 2
2 3
4 5

5 1
2 5
Sample Output
2
4
这是一道很典型的并查集的例题。我们可以发现题目的意思很直白,就是把两两配对(认识)的人合成一个集合,再去找他们共同的父亲(都认识的人),从而找出共有几个由一个祖宗散发开来的“朋友圈”,从而解决问题。
代码如下。

#include<cstdio>
#include<iostream>
#include<stack>
#include<cstring>
using namespace std;
long int pre[500008],n,m;
void restart(long int n){//初始化
	for(int i=0;i<=n;i++){
		pre[i]=i;
	}
}
long int find1(long int t){//通过递归找祖宗
	while(pre[t]!=t){
		t=pre[t];
	}
	return t;
}
void merge1(long int u,long int v){//把认识的人放在一个集合
	long int t1=find1(u);
	long int t2=find1(v);
	if(t1!=t2) pre[t2]=t1;
}

int main()
{int t;
cin>>t;
while(t--)
{
	int m,n,a,b,r=0;
	cin>>m>>n;
	restart(m);
	for(int i=1;i<=n;i++)
	{
		cin>>a>>b;
		merge1(a,b);
	}
	for(int i=1;i<=m;i++)
	{
		if(find1(i)==i)r++;
	}
	cout<<r<<endl;
}
}

另外就是有些题目有多重判断条件,可以考虑把并查集的数组开多倍(比如上述的pre数组。类体如下。
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。
第二种说法是"2 X Y",表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
Input
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
Output
只有一个整数,表示假话的数目。
Sample Input
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
Sample Output
3

这题我们可以看到要查找的x和y元素有着多种关系,并且题目中的食物链的设定也等同于一个硬性条件,比如a吃b,b吃c,反过来c就一定可以吃掉b。
针对这种情况,我们可以考虑开多个并查集来存放不同关系的x和y的集合。
我们暂且把per[1~n],per[n+1,2n],per[2n+1,3n]当成本身集合(x==y)、捕食者集合(x吃y)和天敌集合(x被y吃)。
这样我们在判断是否矛盾时,只需在给定x=y时,判断x的本身是否在y的捕食者集里面以及y是不是在x的捕食者集里面;同理,在给定x吃y时,判断他们本身是不是同一个集合、以及x是否在y的捕食者集里面就可以了。
代码如下

#include<cstdio>
#include<iostream>
#include<stack>
#include<cstring>
const int MAXN=1e5;
using namespace std;
long int pre1[3*MAXN],n,m;
void restart(long int n) {
	for(int i=0; i<=3*n; i++) {
		pre1[i]=i;

	}
}
long int find1(long int t) { //zhao zhu gen
	while(pre1[t]!=t) {
		t=pre1[t];
	}
	return t;
}

void merge1(long int u,long int v) { //jihe fangyi qi
	long int t1=find1(u);
	long int t2=find1(v);
	if(t1!=t2) pre1[t2]=t1;
}
int main() {
	int n,k,k1=0;
	cin>>n>>k;
	restart(n);
	while(k--) {
		int d,x,y;
		scanf("%d%d%d",&d,&x,&y);
		if(x>n||y>n) {
			k1++;
		} else {

			if(d==1) {
				if(find1(x+n)==find1(y)||find1(x)==find1(y+n)) {
					//x chi t ||y chi x
					k1++;
				} else {
					merge1(x,y);
					merge1(x+n,y+n);
					merge1(x+2*n,y+2*n);
				}
			} else {
				if(find1(x)==find1(y)||find1(x)==find1(y+n)) {
					k1++;

				} else {
					merge1(x,y+2*n);
					merge1(x+n,y);
					merge1(x+2*n,y+n);
				}
			}
		}

	}
	cout<<k1<<endl;
}

以上就是我对并查集的一些知识点的总结,有关加权并查集的内容自己还不太熟悉,以后争取补上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值