并查集的应用(一)

本文介绍了并查集的基本概念、主要操作,包括初始化、查找和合并,并通过家族关系和动物食物链两个实例展示了其具体应用。文章还讨论了在处理假话问题时,并查集如何帮助确定矛盾条件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



 

一 理论知识

1  定义: 并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)

                的合并及查询问题。

   2  主要操作: 

        初始化:   把每个点所在集合初始化为其自身。通常来说,这个步骤在每次

                       使用该数据结构时只需要一次,无论何种实现方式,时间复杂度

                       均为 O(N)。

        查找:      查找元素所在的集合,即根节点。
        合并:      将两个元素所在的集合合并为一个集合。

  3  简单实现:

 

int par[MAX_N], rank[MAX_N];  
        //加了一个rank数组记录树的高度 
	//避免像二叉搜索树退化成单链表 
	//注意在题目数据过大时,可以不   
	//记录树的高度,否则会超时。
void Init(int n){
    int i;
    for(i=0; i<n; i++){
      par[i]=i;
      rank[i]=0;
    }
}

int Find(int x){    //注意体会在递归的过程中进行路径压缩
  if(x!=par[x])
         par[x]=Find(par[x]);  
  return par[x];
}


void unite(int x, int y){
   x=Find(x);
   y=Find(y);

  if(x==y)  return;

   if(rank[x]<rank[y]){
      par[x]=y;
   }
   else{
      par[y]=x;
      if(rank[x]==rank[y])
        rank[x]++;
   }
}

        

二  简单应用

1. 家族关系(最简单的应用)

   描述
          若某个家族人员过于庞大,要判断两个是否是亲戚,确实

   还很不容易,现在给出某个亲戚关系图,求任意给出的两个人

  是否具有亲戚关系。 规定:x和y是亲戚,y和z是亲戚,那么x

  和z也是亲戚。如果x,y是亲戚,那么x的亲戚 都是y的亲戚,

   y的亲戚也都是x的亲戚。

  输入格式
         第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),

  分别表示有n个人,m个亲戚关系,询问p对亲戚关系。以下m行:

   每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Ai和Bi具有亲戚关系。

  接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。


   输出格式
        P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”

   或“不具有”亲戚关系。


   输入样例:

   6 5 3
   1 2
   1 5
   3 4
   5 2
   1 3
   1 4
   2 3
   5 6

   输出样例:

   Yes
   Yes
   No

  参考代码:

#include<stdio.h>
#define MAX_N 5555

int par[MAX_N],rank[MAX_N];

void Init(int n){
	int i;
	for(i=0; i<n; i++){
		par[i]=i;
		rank[i]=0;
	}
}

int Find(int x){
	if(x!=par[x])
	   par[x]=Find(par[x]);
	   
	return par[x];
}

void Unite(int x, int y){
	x=Find(x);
	y=Find(y);
	
	if(x==y)  return;
	
	if(rank[x]<rank[y]){
		par[x]=y;
	}
	else{
		par[y]=x;
		if(rank[x]==rank[y])
		   rank[x]++;
	}
}

bool same(int x, int y){
	return Find(x)==Find(y);
}

int main(){
	int j,N,m,p,a,b;
	bool  Is_r[MAX_N];
	
	scanf("%d %d %d", &N, &m, &p);
	Init(N);
	for(j=0; j<m; j++){
		scanf("%d %d", &a, &b);
		Unite(a,b);
	}
	
	for(j=0; j<p; j++){
		scanf("%d %d", &a, &b);
		Is_r[j]=same(a, b);
	}
	
	for(j=0; j<p; j++){
		if(Is_r[j])
		    printf("Yes\n");
		else
		    printf("No\n");
    }
    return 0;
}

2. 食物链 (POJ 1182)

   Description

       动物王国中有三类动物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

  解题思路: 

       并查集是维护”属于同一组“的数据结构,但是本题中,并不只有属于同一类的信息,还有捕食关系的存在。

       对于每只动物 i 创建3个元素 i-A , i-B, i-C, 并用这3*N个元素建立并查集。这个并查集维护如下信息:

        1) i-X表示” i属于种类X“.

        2) 并查集里的每一个组表示组内所有元素代表的情况都同时发生或不发生。

       例如: 如果i-A 和 j-B在同一个组里,就表示如果i属于种类A那么j一定属于种类B,如果j属于种类B

    那么i一定属于种类A。因此,对每一条信息可以进行如下操作:

    第一种, x 和 y属于同一类,  合并 x-A 和 y-A,  合并 x-B 和 y-B, 合并 x-C  和 y-C。

    第二种, x吃y,                    合并 x-A 和 y-B,  合并 x-B 和 y-C, 合并 x-C  和 y-A。

   再者,在合并之前需要先判断合并是否会产生矛盾。


  参考代码:

 

#include<stdio.h>
#define MAX_N 50005

int par[MAX_N*3],rank[MAX_N*3];
int T[MAX_N<<1], X[MAX_N<<1],Y[MAX_N<<1];

void Init(int n){
	int i;
	for(i=0; i<n; i++){
		par[i]=i;
		rank[i]=0;
	}
}

int Find(int x){
	if(x!=par[x])
	   par[x]=Find(par[x]);
	   
	return par[x];
}

void unite(int x, int y){
	x=Find(x);
	y=Find(y);
	
	if(x==y)  return;
	
	if(rank[x]<rank[y]){
		par[x]=y;
	}
	else{
		par[y]=x;
		if(rank[x]==rank[y])
		   rank[x]++;
	}
}

bool same(int x, int y){
	return Find(x)==Find(y);
}

int main(){
	int j,N,K,p,a,b,ans;

	
	scanf("%d %d ", &N, &K);
	
	Init(N*3);
	
	ans=0;
	for(j=0; j<K; j++){
		scanf("%d %d %d", &p, &a, &b);
		a=a-1;
		b=b-1;
		
		//不正确的编号 
		if(a<0||a>=N||b<0||b>=N){
			ans++;
			continue;
		}
		
		if(p==1){
			//“X和y属于同一类”的信息 
			if(same(a, b+N)||same(a,b+2*N)){
				ans++;
			}
			else{
				unite(a, b);
				unite(a+N, b+N);
				unite(a+2*N, b+2*N);
			}
		}
		 else{
		 	//“x吃y”的信息 
		 	if(same(a, b)||same(a, b+2*N)){
		 		ans++;
		 	}
		 	else{
		 		unite(a, b+N);
		 		unite(a+N, b+2*N);
		 		unite(a+2*N, b);
		 	}
		 }	
	}
	
	printf("%d\n", ans);
    return 0;
}
	  




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值