种类并查集&带权并查集

P2024 (NOI2001)食物链
题目描述
动物王国中有三类动物 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 句话有的是真

的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

• 当前的话与前面的某些真的话冲突,就是假话

• 当前的话中 X 或 Y 比 N 大,就是假话

• 当前的话表示 X 吃 X,就是假话

你的任务是根据给定的 N 和 K 句话,输出假话的总数。

输入格式
从 eat.in 中输入数据

第一行两个整数,N,K,表示有 N 个动物,K 句话。

第二行开始每行一句话(按照题目要求,见样例)

输出格式
输出到 eat.out 中

一行,一个整数,表示假话的总数。

输入输出样例
输入 #1 复制
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
输出 #1 复制
3
说明/提示
1 ≤ N ≤ 5 ∗ 10^4

1 ≤ K ≤ 10^5

种类并查集的意义
并查集能维护连通性、传递性,通俗地说,亲戚的亲戚是亲戚。

然而当我们需要维护一些对立关系,比如 敌人的敌人是朋友 时,正常的并查集就很难满足我们的需求。

这时,种类并查集就诞生了。

常见的做法是将原并查集扩大一倍规模,并划分为两个种类。

在同个种类的并查集中合并,和原始的并查集没什么区别,仍然表达他们是朋友这个含义。

考虑在不同种类的并查集中合并的意义,其实就表达 他们是敌人 这个含义了。

按照并查集美妙的 传递性,我们就能具体知道某两个元素到底是 敌人 还是 朋友 了。
种类并查集思路
就是用3倍的并查积的存各种动物的关系

一倍存本身,二倍存猎物,三倍存天敌

唯一容易忽略的点就是:自己的猎物的猎物 就是自己的天敌

那么我们每次只要维护三个并查积的关系就可以了

种类并查集题解

#include
using namespace std;
const int MAXN=2e5+10;
int fa[MAXN];
int n,k,opt,x,y;
int read()          //读入优化
{
	char ch;
	int n=0;
	ch=getchar();
	while(ch<'0'||ch>'9'){ch=getchar();	}
	while(ch>='0'&&ch<='9'){
		n=((n<<3)+(n<<1)+(ch&15));
		ch=getchar();
	}
	return n;
}
int find(int x)           //查询
{
	while(x!=fa[x])
	{
		x=fa[x];
	}
	return x;
}
int main()
{
	int ans=0; 
	n=read();
	k=read(); 
	for(int i=1;i<=3*n;i++)      
	fa[i]=i;
	//对于每种生物:设 x 为本身,x+n 为猎物,x+2*n 为天敌
	for( ;k ;k--)
	{
		opt=read();
		x=read();
		y=read();
		if(x>n||y>n){      // 不属于该食物链显然为假
			ans++;
			continue;
		}
		if(opt==1)
		{
			if(find(x+n)==find(y)||find(x+2*n)==find(y)){   
				ans++;
				continue;
			}
			//如果1是2的天敌或猎物,显然为谎言
			fa[find(x)]=find(y);
			fa[find(x+n)]=find(y+n);
			fa[find(x+2*n)]=find(y+2*n);
			//如果为真,那么1的同类和2的同类,1的猎物是2的猎物,1的天敌是2的天敌
		} 
		if(opt==2)
		{
			if(find(x)==find(y)||find(x+2*n)==find(y)){
				ans++;
				continue;
			}
			//如果1是2的同类或猎物,显然为谎言
			fa[find(x)]=find(y+2*n);
			fa[find(x+n)]=find(y);
			fa[find(x+2*n)]=find(y+n);
			//如果为真,那么1的同类是2的天敌,1的猎物是2的同类,1的天敌是2的猎物
		}
	}
	cout<

带权并查集的意义
上网大概搜了下,对带权并查集的诠释是这样的:

在对并查集进行路径压缩和合并操作时,这些权值具有一定属性,即可将他们与父节点的关系,变化为与所在树的根结点关系。
也就是说,权值代表着当前节点与父节点的某种关系(即使路径压缩了也是这样),通过两者关系,也可以将同一棵树下两个节点的关系表示出来。

带权并查集思路
由题意得,动物一共只有A,B,C三种,也就是说只要确定了一种动物的种类和他们的关系(即权值),其他的动物的种类也就知道了。

我们用re数组表示编号i与父亲节点的权值关系,由于只有三种动物,所以权值也只有三种:0–>同种动物,1–>捕食关系,2–>被捕食关系,转移时便可以采用对3取模来实现。(初始化为0,即自己与自己为同种动物)
带权并查集题解

#include
#include
using namespace std;
const int MAXN=2e5+10;
int fa[MAXN],d[MAXN];    //0-->同种动物,1-->捕食关系,2-->被捕食关系。
int read()
{
	int n=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9'){
		n=((n<<1)+(n<<3)+(ch&15));
		ch=getchar();
	}
	return n;
 } 
int find(int x)
{
	if(x==fa[x])
	{
		d[x]=0;
		return x;
	}
	int f=find(fa[x]);
	d[x]=(d[x]+d[fa[x]])%3;
	fa[x]=f;
	return f;
	
}

int main()
{
	   int n,k,opt,x,y;
	   int ans=0;
	   n=read();
	   k=read();
//	   cout<0 ; k--)
	   {
	   	  opt=read();
	   	  x=read();
	   	  y=read();
	   	  if(x>n||y>n||(opt==2&&x==y)){
	   	  	 ans++;
	   	  	 continue;
		  }
		  if(opt==1)
		  {
		  	int f_x=find(x);
		  	int f_y=find(y);
		  	if(f_x==f_y&&d[x]!=d[y]){ //判断是否在同一棵树及两者是否为同种动物。
		  		ans++;
		  		continue;
			}
			else if(f_x!=f_y){   //合并
				fa[f_x]=f_y;
				d[f_x]=(3-d[x]+d[y])%3;
			}
		   }	
			if(opt==2)
			{
				int f_x=find(x);
				int f_y=find(y);
				if(f_x==f_y&&(3+d[x]-d[y])%3!=1)  //用两个节点与父亲的关系推出两者关系
				{
					ans++;
					continue;
				}
				else if(f_x!=f_y){  //合并 
					fa[f_x]=f_y;
					d[f_x]=(3-d[x]+d[y]+1)%3; 
				}
			}
		  	
		  	
		  
	   }
	   cout<

你可能感兴趣的:(图论-并查集)