食物链 并查集

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

(参考博客,谢谢大佬,自己再次写了一下自己的一点儿理解)

 

https://blog.csdn.net/ShiWaiGaoRen12345/article/details/51202250 

菜鸟分析:

有三类动物A,B,C,构成食物链:A吃B, B吃C,C吃A。
输入:
第一行是两个整数N和K,以一个空格分隔
以下K行每行是三个正整数 D,X,Y,。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
输出:假话的数目
列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
代码:
三个数字代表三种关系(x是一个数字)
x表示x属于A类,
x+n表示x属于B类,
x+2*n表示x属于C类
合并关系,就直接相当于合并数字

#include<cstdio>
#include<iostream> 
using namespace std;
int fa[150010];
int find(int x){
	if(x==fa[x]){
		return x;
	}//x的father为自身 
	return fa[x]=find(fa[x]);//不是自身继续找 
}
//same函数 判断x y是否是同一个father,即是否同类,同类就为同一个father 
int same(int x, int y){
	if(find(x)==find(y))
		return 1;
	return 0;
}
//bind函数将x y组合,即为同一个father 
void bind(int x, int y){
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy)//两数不在同一集合
	fa[fx]=fy;//合并 同类合并为同一根结点 .
	//int z=find(x);//z为x的father 
	//fa[z]=find(y);//z的father为y的father,即x的father的father为y的father
}//组合 
int main(){
	int n, k, no, x, y;
	int ans=0;
	scanf("%d%d", &n, &k);
	//开辟了3*n的数组 
	for(int i=1; i<=3*n; i++){
		fa[i]=i;//未进行合并,所有数字属于不同的集合 
	}//初始化并查集 
	for(int j=1; j<=k; j++){
		scanf("%d%d%d", &no, &x, &y);
		if(x<1||x>n||y<1||y>n){
			ans++;//当前的话中X或Y比N大,就是假话;正整数
			continue;
		}
		if(no==1){
			/* x y+n y+2*分别为A类,B类 ,C类所以不应该为同一类,这是前提条件 
			情况1 同类所分的三类不能同 
			same(x, y+n)=same(A, B)即x可以吃y
			same(x, y+2*n)==same(A, C)*/ 
			if(same(x, y+n)||same(x, y+2*n)){
				ans++;
			}//错误情况 
			//1 x y 同类,如果是对的则以下三种情况 
			else{
				bind(x, y);//A类 
				bind(x+n, y+n);//B类 
				bind(x+2*n, y+2*n);//C类 
			}//正确情况 
		}
		//2 x y,表示X吃Y
		else{
			/*情况2有两种错误情况 1.X吃X同类吃 2. A吃C不符合食物链 
			1.same(x, y)=same(A, A)2 A A (X吃X,假话);
			2.same(x, y+2*n)=same(A, C)2 A C(A吃C,假话)*/ 
			if(same(x, y)||same(x, y+2*n)){
				ans++;
			}//错误情况
			else{
				bind(x, y+n);//A吃B
				bind(x+n, y+2*n);//B吃C
				bind(x+2*n, y);//C吃A
			}//正确情况 
		}
	}
	printf("%d\n", ans);
	return 0;
}
 

代码补充说明:刚开始是代表A B C代表吃的顺序,后来由于unite函数,A B C的关系从绝对关系由于unite函数变成了相对关系

若1 x y是错误的直接看是有same(x, y+n)或same(y+2*n, x),same这边表示两个元素在同一个集合。

same(x, y+n)表示x可以吃y;同理same(y+2*n, x)表示y可以吃x;

因为unite的操作,一旦有x吃y,接着所有的情况也被包括了。

bind(x, y+n);//A吃B

bind(x+n, y+2*n);//B吃C

bind(x+2*n, y);//C吃A

所以same(x, y+n)其实同时也包含了same(x+n, y+2*n)以及

same(y+2*n, x);就是等于同时把所有情况也都举了。

案例分析:

1.手动

100 7

1 101 1(X或Y比N大,就是假话)❌     x>n ans++

2 1 2   (1吃2)                                       bind(1,2)

2 2 3 (2吃3)                                    bind(2,3)

2 3 3 (X吃X,就是假话)❌            same(3,3)  same(x,y)  ans++

1 1 3 (1 3同类)❌                          same(1,3)  same(x,y+2*n)  ans++

2 3 1 (3吃1符合食物链)                  bind(3,1)  适合食物链

1 5 5(5 5同类)                                bind(5,5)

所以假话3

不加注释代码:

#include<cstdio>
#include<iostream> 
using namespace std;
int fa[150010];
int find(int x){
	if(x==fa[x]){
		return x;
	}
	return fa[x]=find(fa[x]);
}

int same(int x, int y){
	if(find(x)==find(y))
		return 1;
	return 0;
} 
void bind(int x, int y){
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy)
	fa[fx]=fy;
int main(){
	int n, k, no, x, y;
	int ans=0;
	scanf("%d%d", &n, &k);
	for(int i=1; i<=3*n; i++){
		fa[i]=i;
	}
	for(int j=1; j<=k; j++){
		scanf("%d%d%d", &no, &x, &y);
		if(x<1||x>n||y<1||y>n){
			ans++;
			continue;
		}
		if(no==1){
			if(same(x, y+n)||same(x, y+2*n)){
				ans++;
			}
			else{
				bind(x, y); 
				bind(x+n, y+n); 
				bind(x+2*n, y+2*n);
			} 
		}
		else{
			if(same(x, y)||same(x, y+2*n)){
				ans++;
			}
			else{
				bind(x, y+n);
				bind(x+n, y+2*n);
				bind(x+2*n, y);
			}
		}
	}
	printf("%d\n", ans);
	return 0;
}
 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clark-dj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值