[模板] 并查集 - 种类并查集 (洛谷 P2024 食物链)

P2024 [NOI2001]食物链

P2024 食物链 - 洛谷

题目描述

  动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。ABBCCA

  现有N个动物,以1 - N编号。每个动物都是A, B, C中的一种,但是我们并不知道它到底是哪一种。

  有人用两种说法对这N个动物所构成的食物链关系进行描述:

  • 第一种说法是1 X Y,表示XY是同类。
  • 第二种说法是2 X Y,表示XY

  此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

  • 当前的话与前面的某些真的话冲突,就是假话
  • 当前的话中XYN大,就是假话
  • 当前的话表示XX,就是假话

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

输入格式

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

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

输出格式

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

输入输出样例

输入输出
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
3

说明/提示

1≤N≤5×1E4, 1≤K≤1E5

思路

ABC三个集合分不同空间存放,对于每次询问判断其真实性;如果询问为真,那么在3个集合中建立关系,重复操作。

参考:题解 P2024 【食物链】 - Sooke’s Blog - 洛谷博客

对于动物 x 和 y,我们可能有 x 吃 y, x 与 y 同类, x 被 y 吃。

······

我们将并查集分为 3 个部分,每个部分代表着一种动物种类。

设我们有 n 个动物,开了 3n 大小的种类并查集,其中 1∼n 的部分为 AA 群系, n+1∼2n 的部分为 B 群系, 2n+1∼3n 的部分为 C 群系。

······

当 A 中的 x 与 B 中的 y 合并,有关系 x 吃 y;当 C 中的 x 和 C 中的 y 合并,有关系 x 和 y 同类等等……

但仍然注意了!我们不知道某个动物属于 A, B,还是 C,我们 3 个种类都要试试!

也就是说,每当有 1 句真话时,我们需要合并 3 组元素。

容易忽略的是,题目中指出若 x 吃 y, y 吃 z,应有 x 被 z 吃。

这个关系还能用种类并查集维护吗?答案是可以的。

若将 x 看作属于 A,则 y 属于 B, z 属于 C。最后,根据关系 A 被 C 吃可得 x 被 z 吃。

······

对于样例的图片解释:

代码

  用的并查集类和一般的并查集是一样的
[模板] 并查集 - 一般并查集 (洛谷 P3367 并查集)

//c++
#include <cstdio>

struct ufsets_elem{ //并查集元素
	ufsets_elem *root;
	ufsets_elem(){
		root = this;
	}
	ufsets_elem *find(){
		return root == this ? root : root = root->find(); //路径压缩
	}
};

struct ufsets{	//并查集
protected:
	int ufsets_num;			  //独立集合数量
	ufsets_elem *ufsets_base; //并查集数组
public:
	explicit ufsets(const int &n){
		ufsets_base = new ufsets_elem[n + 1]();
		ufsets_num = n;
	}
	bool catenate(const int &a, const int &b){ //合并a,b集合,返回false则说明两元素已是同一集合
		if (ufsets_base[a].find() != ufsets_base[b].find()){ //若a,b不在同一集合则合并b至a
			--ufsets_num;
			ufsets_base[b].root->root = ufsets_base[a].root;
			return true;
		}
		return false;
	}
	bool relative(const int &a, const int &b){ //查询是否在同一个集合中,是则返回true
		return ufsets_base[a].find() == ufsets_base[b].find();
	};
	int size(){//独立集合数量
		return ufsets_num;
	}
	~ufsets(){
		delete[] ufsets_base;
	}
};

int main(){
	int n, k, r = 0;
	int tmp1, tmp2, tmp3;
	scanf("%d%d", &n, &k);
	ufsets u(n * 3);
	for (int i = 1; i <= k; i++){
		scanf("%d%d%d", &tmp1, &tmp2, &tmp3);
		if (tmp2 > n || tmp3 > n){
			r++;
			continue;
		}
		switch (tmp1){
		case 1:																  //同类关系
			if (u.relative(tmp2 + n, tmp3) || u.relative(tmp2 + n * 2, tmp3)) //假话
				r++;
			else //真话
				u.catenate(tmp2, tmp3), u.catenate(tmp2 + n, tmp3 + n), u.catenate(tmp2 + n * 2, tmp3 + n * 2);
		break;
		case 2:															  //捕食关系
			if (u.relative(tmp2, tmp3) || u.relative(tmp2 + n * 2, tmp3)) //假话
				r++;
			else //真话
				u.catenate(tmp2, tmp3 + n * 2), u.catenate(tmp2 + n, tmp3), u.catenate(tmp2 + n * 2, tmp3 + n);
		break;
		}
	}
	printf("%d\n", r);
	return 0;
}
//java
import java.util.*;
import java.math.*;

class ufsets_elem{ //并查集元素
	public ufsets_elem root;
	ufsets_elem(){
		root = this;
	}
	ufsets_elem find(){
		return root == this ? root : (root = root.find()); //路径压缩
	}
};

class ufsets{	//并查集,依赖于class ufsets_elem
	int ufsets_num;			  //独立集合数量
	ufsets_elem[] ufsets_base; //并查集数组
	ufsets(final int n){
		ufsets_base = new ufsets_elem[n + 1];
		for(int i=1;i<=n;i++)//ufsets_base[0]用不到
			ufsets_base[i]=new ufsets_elem();
		ufsets_num = n;
	}
	boolean catenate(final int a, final int b){ //合并a,b集合,返回false则说明两元素已是同一集合
		if (ufsets_base[a].find() != ufsets_base[b].find()){ //若a,b不在同一集合则合并b至a
			--ufsets_num;
			ufsets_base[b].root.root = ufsets_base[a].root;//b更改的是其父节点的<root>
			return true;
		}
		return false;
	}
	boolean relative(final int a, final int b){ //查询是否在同一个集合中,是则返回true
		return ufsets_base[a].find() == ufsets_base[b].find();
	};
	int size(){//独立集合数量
		return ufsets_num;
	}
};

public class Main {
	public static void main(String[] args) {
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt(), k=sc.nextInt(),r=0;
		int tmp1, tmp2, tmp3;
		ufsets u=new ufsets(n * 3);
		for (int i = 1; i <= k; i++){
			tmp1=sc.nextInt();
			tmp2=sc.nextInt();
			tmp3=sc.nextInt();
			if (tmp2 > n || tmp3 > n){
				r++;
				continue;
			}
			switch (tmp1){
			case 1:																  //同类关系
				if (u.relative(tmp2 + n, tmp3) || u.relative(tmp2 + n * 2, tmp3)) //假话
					r++;
				else { //真话
					u.catenate(tmp2, tmp3);
					u.catenate(tmp2 + n, tmp3 + n);
					u.catenate(tmp2 + n * 2, tmp3 + n * 2);
				}
				break;
			case 2:															  //捕食关系
				if (u.relative(tmp2, tmp3) || u.relative(tmp2 + n * 2, tmp3)) //假话
					r++;
				else { //真话
					u.catenate(tmp2, tmp3 + n * 2);
					u.catenate(tmp2 + n, tmp3);
					u.catenate(tmp2 + n * 2, tmp3 + n);
				}
					break;
			}
		}
		System.out.println(r);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值