种类并查集与带权并查集

文章目录



带权并查集

在基础并查集上增加了权值,在对并查集进行路径压缩和合并操作时,这些权值具有一定属性,即可将他们与父节点的关系,变化为与所在树的根结点关系。

也就是说,权值代表着当前节点与父节点的某种关系(即使路径压缩了也是这样),通过两者关系,也可以将同一棵树下两个节点的关系表示出来。

个人感觉比较难理解。



种类并查集

元素分为不同种类的并查集,实际上可以转化为带权并查集来处理。当然也有种类并查集自己的解法,常见的做法是分为 n 个种类,就将原并查集扩大 n 倍规模。

一般就几个状态而已,做法比较好理解。




例题


【POJ】1703 Find them, Catch them

原题链接:http://poj.org/problem?id=1703


题意: 一个城市有两个帮派,n,k 分别表示 n 个人,k 次询问。例如, A 1 2 是询问 1 和 2 这两个人是不是一个帮派,然后输出。D 1 2 是表示这两个人不在一个帮派。

注意: 再次因为没加 getchar() 被卡了好久,一直报RE错误。。。这个坑下次一定不能再掉了。。。还有,要用 scanf、printf 输入输出格式,不然会TLE。


1、种类并查集做法:

#include <iostream>
#include <cstdio>
using namespace std; 
const int N=100100;
int n,m;
int r[N*2];
int find(int x){
	if(r[x]==x)	
		return x;
	else
		return r[x]=find(r[x]);
}
void merge(int x,int y){
	int ra=find(x);
	int rb=find(y);
	if(ra!=rb)
		r[ra]=rb;
}

bool same(int a,int b){
	return find(a)==find(b);
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=2*N;i++)
			r[i]=i; 
		while(m--){
			//再次在这里掉坑!!!谨记!!不加这个会RE或者WA,凡是样例能过!!! 
			getchar();	
			char ch;
			scanf("%c",&ch);
			int a,b;
			scanf("%d%d",&a,&b);
			if(ch=='D'){
				merge(a,b+n);
				merge(a+n,b);
			}else{
				if(same(a,b)){	//a、b同类 
					printf("In the same gang.\n");
					continue;
				}
				if(same(a,b+n)) //a、b不同类 
					printf("In different gangs.\n");
				else	
					printf("Not sure yet.\n");
			}
		}
	}
	return 0;
}

2、带权并查集做法:




【POJ】1182 食物链

原题链接:http://poj.org/problem?id=1182


题意: 中文题目,自己看原题即可,这里就不啰嗦了。

思路: 这道题可以用种类并查集来解,也可以用带权并查集来做。要注意的就是要用 scanf、printf 输入输出格式,不然会TLE。


1、种类并查集做法:

推荐大佬的博客:POJ - 1182: 食物链 (并查集)

#include <iostream>
#include <cstdio>
using namespace std;
const int N=50010;
int n,k;
int r[N*3];
int find(int x){
	int t=x;
	while(r[t]!=t)
		t=r[t];
	while(x!=t){	//路径压缩 
		int temp=r[x];
		r[x]=t;
		x=temp;
	}
	return t;
} 
void merge(int x,int y){
	int roota=find(x);
	int rootb=find(y);
	if(roota != rootb)
		r[roota]=rootb;
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=3*n;i++)
		r[i]=i;
	int ans=0;
	for(int i=1;i<=k;i++){
		int op,a,b;
		scanf("%d%d%d",&op,&a,&b);
		if(a<1||a>n||b<1||b>n){	//不在范围内,假话 
			ans++;
			continue;
		}
		if(op==2 && a==b){	// 如果是同类,a还吃b,假话 
			ans++;
			continue;
		}
		if(op==1){	//a、b同类 
			if(find(a)==find(b+n) || find(b)==find(a+n))	//如果a吃b或者b吃a,假话 
				ans++;
			else{	//真话,建立a和b同类的关系 
				merge(a,b);
				merge(a+n,b+n);
				merge(a+2*n,b+2*n);
			}
		}else if(op==2){	//a吃b 
			if(find(a)==find(b) || find(b)==find(a+n))	///如果a、b同类或者b吃a,假话 
				ans++;
			else{	//真话,建立a吃b的关系 
				merge(a,b+n);
				merge(a+n,b+2*n);
				merge(a+2*n,b);
			}
		}
	}
	printf("%d\n",ans);
	return 0;
} 

2、带权并查集做法:

推荐大佬的博客:

  1. POJ-1182 食物链
  2. 食物链-poj1182(带权并查集经典模板)
  3. 【POJ1182】食物链,思路+数据+代码,可能是史上关于这道题最详细的解题报告
#include <iostream>
#include <cstdio>
using namespace std;
const int N=50010;
int n,k;
int r[N],rel[N];
void init(){
	for(int i=1;i<=n;i++){
		r[i]=i;
		rel[i]=0;
	}
} 
int find(int x){
	if(x==r[x])
		return x;
	int t=r[x];
	r[x]=find(t);
	rel[x]=(rel[x]+rel[t])%3;
	return r[x];
}
int main(){
	scanf("%d%d",&n,&k);
	init();
	int ans=0;
	int op,a,b;
	for(int i=1;i<=k;i++){
		scanf("%d%d%d",&op,&a,&b);
		if(a>n || b>n){
			ans++;
			continue;
		}
		if(op==2 && a==b){
			ans++;
			continue;
		}
		int roota=find(a);
		int rootb=find(b);
		if(roota != rootb){
			r[rootb]=roota;
			rel[rootb]=(3+(op-1)+rel[a]-rel[b])%3;
		}else{
			if(op==1 && rel[a]!=rel[b])
				ans++;
			else if(op==2 && (3-rel[a]+rel[b])%3!=op-1)
				ans++;
		}
	}
	printf("%d",ans);
	return 0;
}


上一篇:HTML链接


下一篇:前端-html