食物链(种类并查集)

一般我们的并查集是可以维护这样的关系的,比如亲戚的亲戚是亲戚,只需要简单调用并查集就可以实现。但如果我们现在想要维护一个这样的关系,比如敌人的敌人是朋友,那一般的并查集就不行了,对于一般的并查集而言,它只能维护敌人的敌人是敌人,或者是朋友的朋友是朋友,对于敌人的敌人是朋友这种关系是无法维护的,那么为了解决这种问题,就引出了种类并查集去解决这个问题。
例题:洛谷P2024

链接: https://www.luogu.com.cn/problem/P2024

动物王国中有三类动物 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 句话,输出假话的总数。
输入格式
第一行两个整数,N,K,表示有 N 个动物,K 句话。
第二行开始每行一句话(按照题目要求,见样例)
输出格式
一行,一个整数,表示假话的总数。
输入输出样例
输入 #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 ∗ 104
1 ≤ K ≤ 105
对于动物 x 和 y,我们可能有 x 吃 y,x 与 y 同类,x 被 y 吃。但由于关系还是明显的,1 倍大小、2 倍大小的并查集都不能满足需求,3 倍大小不就行了!类似上面,我们将并查集分为 3 个部分,每个部分代表着一种动物种类。设我们有 n 个动物,开了 3n 大小的种类并查集,其中1∼n 的部分为 A 群系,n+1∼2n 的部分为 B 群系,2n+1∼3n 的部分为 C 群系。我们可以认为 A 表示中立者,B 表示生产者,C 表示消费者。此时关系明显:A 吃 B,A 被 C 吃。当然,我们也可以认为 B 是中立者,这样 C 就成为了生产者,A 就表示消费者。但仍然注意了!我们不知道某个动物属于 A,B,还是 C,我们 3 个种类都要试试!也就是说,每当有 1 句真话时,我们需要合并 3 组元素。容易忽略的是,题目中指出若 x 吃 y,y 吃 z,应有 x 被 z 吃。若将 x 看作属于 A,则 y 属于 B,z 属于 C。最后,根据关系 A 被 C 吃可得 x 被 z 吃。
举个栗子:
设有四个动物,1是2的天敌(√),1和4是同类(√),1和2是同类(×),1和2是天敌(×),先设初始的并查集为(1 - 4表示四个群系,5 - 8表示猎物,9 ~ 12表示天敌):
14表示四个群系,58表示猎物,9~12表示天敌
(1)若1吃了2,并查集维护之后为(2就要连1的猎物,1就要连2的天敌):
在这里插入图片描述
(2)若1和4是同类(那么1和4就要连线,推出1和4的猎物也是同类,连线,1和4的天敌也是同类,连线)
在这里插入图片描述
(3)现在给出了1和2是同类,显然与前面相悖,那么我们怎么根据以已经画号图来判断呢,我们通过推理可以知道,如果1不是2的猎物,2也不是1的猎物,那1和2肯定是同类,因此我们可以通过判断1+4和2是否在一个集合,2+4和1是否在一个集合去判断它是否成立。即1的猎物集合有没有2,2的猎物集合有没有1,如果它们之中有一个条件成立了,显然1和2就不是同类,如果都不成立,那么1和2就是同类
(4)现在再给出2吃了1,也是一个错误的信息。那么同上,我们怎么用画好的图就判断它们是否在一个集合中呢,如果1和2不是同类且1不吃2,那么显然2吃1是成立的,所以我们要根据图判断一下,1和2是否在一个集合中,1+4是否和2在一个集合中,如果上面两个条件都不成立,那么1吃2成立,如果上面两个条件有一个成立了,那么1吃2不成立。

注意事项
种类并查集求的并非具体种类,而是关系!
在代码过程中,不要忘了特判编号大于 n 的情况!
就这个题而言需要用scanf和printf或者用写快读函数,用cin和cout会TLE!
本人写的是按秩合优化的并查集,正常的并查集模板也是可以的!

并查集模板:

void init()//初始化
{
	for(int i=0;i<MAXN;i++)
	{
		parent[i]=i;
	}
}
int find_root(int t)//查询代表元
{
	int root=t;
	while(parent[root]!=root)
		root=parent[root];
	while(parent[t]!=root)
	{
		int tmp=parent[t];
		parent[t]=root;
		t=tmp;
	}
	return root;
}

void union_root(int a,int b)//合并
{
	int a_root=find_root(a);
	int b_root=find_root(b);
	if(a_root==b_root)
		return ;
	else{
		parent[b_root]=a_root;
	}
	return ;
}

按秩合优化的写法为(judge数组用来记录树的高度):

void init()//初始化
{
	for(int i=0;i<MAXN;i++)
	{
		parent[i]=i;
		judge[i]=0;
	}
}
int find_root(int t)//查询代表元
{
	int root=t;
	while(parent[root]!=root)
		root=parent[root];
	return root;
}

void union_root(int a,int b)//合并
{
	int a_root=find_root(a);
	int b_root=find_root(b);
	if(a_root==b_root)
		return ;
	if(judge[a_root]==judge[b_root])
	{
		parent[b_root]=a_root;
		judge[a_root]++;
	}
	else if(judge[a_root]>judge[b_root])
		parent[b_root]=a_root;
	else parent[a_root]=b_root;
	return ;
}

下面附此题AC代码:

#include<iostream>
#include<memory.h>
#include<math.h>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int MAXN=50000*3+10;
int parent[MAXN];
int judge[MAXN];
int n,k,d,x,y;

void init()
{
	for(int i=0;i<MAXN;i++)
	{
		parent[i]=i;
		judge[i]=0;
	}
}
int find_root(int t)
{
	int root=t;
	while(parent[root]!=root)
		root=parent[root];
	return root;
}

void union_root(int a,int b)
{
	int a_root=find_root(a);
	int b_root=find_root(b);
	if(a_root==b_root)
		return ;
	if(judge[a_root]==judge[b_root])
	{
		parent[b_root]=a_root;
		judge[a_root]++;
	}
	else if(judge[a_root]>judge[b_root])
		parent[b_root]=a_root;
	else parent[a_root]=b_root;
	return ;
}

int main()
{
	int sum=0;
	init();
	scanf("%d%d",&n,&k);
	for(int i=0;i<k;i++)
	{
		scanf("%d%d%d",&d,&x,&y);
		if(x>n||y>n)
		{
			sum++;
			continue;
		}
		if(d==1)
		{
			if(find_root(x)==find_root(y+n)||find_root(y)==find_root(x+n))
			{
				sum++;
			}
			else{
				union_root(x,y);
				union_root(x+n,y+n);
				union_root(x+2*n,y+2*n);
			}
		}
		else if(d==2){
			if(x==y)
			{
				sum++;
				continue;
			}
			if(find_root(x)==find_root(y)||find_root(x)==find_root(y+n))
			{
				sum++;
			}
			else{
				union_root(x,y+2*n);
				union_root(y,x+n);
				union_root(y+n,x+2*n);
			}
		}
	}
	printf("%d\n",sum);
	return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值