#2020.01.13训练题解#STL和并查集(G题)

本文介绍了一道算法题“食物链”,通过使用带权并查集解决动物间复杂的食物链关系判断问题。文章详细解释了如何利用sign数组记录权值,以及在路径压缩时如何更新权值,确保正确判断假话数量。

题源POJ-1182

POJ-1182-食物链

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

题意

  • 输入N和K代表N个动物和K个描述,每种描述是同类和吃的关系
  • 判断并输出假话总数,详细请看中文题面

题解

  • 用sign数组来记录权值,运用带权并查集
  • sign数组中,0表示同类,1表示被父结点吃,2表示吃父结点
  • 路径压缩的时候,由于相关人员无论是爸爸还是儿子都变成了祖先的儿子
  • 所以利用多边形向量关系就可以更新权值
  • baba函数代表这两个动物之间有了关系,至于是什么关系,用sign权值表述
  • 先判断是否属于第种或第种情况,不是的话再调用baba函数判断第
  • 在用baba函数的时候判断这两个动物是否已有关系没有的话就更新权值
  • 如果已有关系,就判断之前的关系和现在的表述是否矛盾
  • 详细请看代码行注释

涉及知识点

  • 带权并查集
  1. 处理 带权并查集 的问题时,关键在于权值的动态变化
  2. 权值表示谁对谁的关系,就用箭头从谁指向谁
  3. 形成多边形关系后,常用向量思维进行表达式的推导
  4. 但要注意如果表达式计算后有负数出现的可能性
  5. 但原来权值要求非负,勿忘加上形成多边形的结点数
  6. 如果需要权值在某一范围内,加多了可能出界
  7. 所以也勿忘形按成多边形的结点数取余
  8. 到底取多少的余和加多少,可根据题意枚举几种情况试一试
  • 形成多边形的向量思维示意图
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

AC代码

#include<stdio.h>
#include<string.h>
const int maxn=50100;
int N,K,D,X,Y,ans;
int father[maxn],sign[maxn];
/*
	本题用到带权并查集,所谓的权值就是记录各个结点之间的关系 
	father数组记录是否与父结点有关系
	sign数组记录与父结点的关系是什么 
	0-同类,1-被父结点吃,2-吃父结点
*/
void init()
{
	for(int i=1;i<=N;i++)
	{
		father[i]=i;//初始化自己都是自己的祖先 
		sign[i]=0;//初始化自己都是自己的同类 
	}
}
int find(int x)//完成"查"的功能,找自己的祖先
{
	if(x!=father[x])
	{
		int p=father[x];//过渡变量,记录father[x]的原值,以便做sign变化 
		father[x]=find(father[x]);
		sign[x]=(sign[x]+sign[p])%3;
	}
	/*
		路径压缩的同时,进行权值更新
		本来的模板是return x==father[x]?x:father[x]=find(father[x]);
		现在为了加上sign的权值变化,分语句清晰 
	*/
	/*
		sign[x]是x对爸爸的关系,路径压缩后,x的爸爸是根结点
		要表示x与根结点的关系,需要考虑原来x对爸爸和爸爸对根结点的关系 
		因为原来的爸爸现在也是根结点的儿子,相当于爸爸成了自己的兄弟
		所以原来的爷孙三代的关系现在就变成了一个三角形
		儿子和爸爸都是爷爷的儿子,假设原来儿子是a,爸爸b,爷爷c
		那么如果ab之间有关系,bc之间有关系
		现在路径压缩后,ac之间也会有关系,也就是新的sign[x]值
		如果a=1,b=2,即a吃b,b被c吃,那么ac同类,新sign[x]=0
		其他同理 
	*/ 
	return father[x]; 
}
void baba(int x,int y,int D)
{
/*
	这题的拜把子并不是说明同一类
	而是说明两结点之间已经有了正确的关系
	只要是数据里给出过信息了就绑到一起
	至于两个结点之间到底是吃、被吃还是同类的关系
	靠sign权值来表述
	同时,为了有fx和fy的存在,在此函数内需要进行是否是第一种情况的判断
	如果不是,则更新结点关系(sign权值)
	至于情况2和3应当在主函数调用baba函数之前已经判断完毕 
*/
	int fx=find(X);
	int fy=find(Y);
	//找出X和Y的祖先是谁
	father[fy]=fx;//连接结点,使得X和Y的find相同,代表他们之间已有关系
	if(fx!=fy) sign[fy]=(sign[x]-sign[y]+D+3)%3;
	/*
		这种情况是没有任何问题的言论
		因为在调用此函数之前已经排除了情况2和3的错误
		现在只可能是与之前的话冲突
		但是还没有关系的两个结点不可能冲突 
		所以此步骤用来实现权值关联
	*/
	/*
		如果祖先不是同一个,说明还无关,给出关系后,他们需要合并
		合并后,Y的祖先,路径压缩后也就是Y的爸爸,他有了爸爸
		这个爸爸是X的祖先,也就是路径压缩后X的爸爸
		那么这四个人形成了四边形的关系(X,X爸爸,Y,Y爸爸)
		他们四个人的关系可以用四边形的向量来解 
		X指向X的爸爸,Y指向Y的爸爸
		Y要与X有关系是题目给出的,题目给了X对Y的关系
		所以D--就是Y指向X的sign
		画图可得,Y指向X的爸爸的权值可用三角形法则求解
		X的爸爸比Y高一级,所以是Y指向X的爸爸 
		要求的是Y的爸爸与X的爸爸,这俩祖先之间的关系
		用最后俩爸爸and一个Y形成的三角形向量求解即可啦
		注意可能出现负值,所以要+3,为了不超出012的范围,要%3 
	*/
	else if((sign[y]-sign[x]+3)%3!=D) ans++;
	/*
		这个时候X和Y的俩祖先相同(路径压缩后同爸爸)
		就说明X和Y已经有关系了,再给一次关系就要考虑会不会产生冲突啦 
		【情况1】当前的话与前面的某些真的话冲突
		这个时候用上述的四边形图,求解Y对于X的sign权值
		然后判断不等于D就行啦 
	*/ 
}
int main()
{
	ans=0;
	scanf("%d %d",&N,&K);
	init();//调用初始化函数 
	for(int i=1;i<=K;i++)
	{
		scanf("%d %d %d",&D,&X,&Y);
		if(X>N||Y>N) ans++;//【情况2】当前的话中X或Y比N大
		else if(D==2&&X==Y) ans++;//【情况3】当前的话表示X吃X
		else//可能是真话,还需判断【情况1】 
		{
			D--;
			/*
				D==1的时候表述同类关系
				D==2的时候表述X吃Y的关系
				但在sign权值里,D==0表述同类,D==1表述吃,D==2表述被吃
				但这是X对Y,在函数里,我们让Y的爸爸认X的爸爸做爸爸 
				所以D--之后,D的值就表示了Y对于X的关系 
			*/
			baba(X,Y,D); 
		}	
	}
	printf("%d\n",ans);
	return 0;
}
/*
	处理带权并查集的问题时,关键在于权值的动态变化
	权值表示谁对谁的关系,就用箭头从谁指向谁
	形成多边形关系后,常用向量思维进行表达式的推导 
	但要注意如果表达式计算后有负数出现的可能性 
	但原来权值要求非负,勿忘加上形成多边形的结点数
	如果需要权值在某一范围内,加多了可能出界
	所以也勿忘形按成多边形的结点数取余喔
	到底取多少的余和加多少,可根据题意枚举几种情况试一试 
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值