POJ-1182-食物链- 经典并查集应用

POJ - 1182
食物链
Time Limit: 1000MS   Memory Limit: 10000KB   64bit IO Format: %I64d & %I64u

Submit Status

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),输出假话的总数。 


用到 并查集的fa[]数组,指的是 当前点x的祖先,

而用一个path【】数组,表示 当前点x与祖先的距离, 如果距离%3==0,与祖先为同类,距离%3==1表示 祖先吃x,距离%3==2表示x吃祖先,

之所以模三 是因为 关系就三种,且 刚好是 连续、构成一个环的关系。  因为关系只有三种  所以 大于等于三的距离,与 其自身对三的取模值 是等价的

【所以一个fa根节点的集合内,他们之间所有节点的联系 是 x 与祖先的 关系】

首先肯定判断是否有 自己吃自己的情况,其次 x、y大于n的情况. 

并且 如果 给出的x与y 根本不在一个集合内,那么无论怎么说,都是合法的(因为是第一次出现)

如果d==1,表示 认为x与y是同类

我们找到他们的根,fx,fy。

如果fx==fy;   那么他们是同一祖先,判断他们是否同类==》 判断他们与祖先的关系是否同样==》 他们与祖先的距离是否一样,即path[x]==path[y];

如果fx!=fy;    那么他们不是同祖先, 新给的关系肯定是合法的,我们只需要维护,也就是 把两个集合合并 (本代码以fy为新根), 并更新fx集合中所有点到 新根fy的距离,更新的过程如下:

首先 原来 y-->fy的距离为f[y]  , x---->fx的距离为f[x],

合并之后的关系是  x---->fx------>fy<--------y;

 因为x与y同类,所以distance(x,y)=0, 

所以他们的【距离关系】可以近似看作这样的情况  y(x)----->fx---->fy;

所以得出  path[fx]=path[y]-path[x]; 前者指y到fy的距离,后者指,x到fx的距离;

如果d==2,表示x吃y

先找根

如果fx==y;   那么他们是同一祖先,判断是否满足x吃y ==》 判断他们与祖先关系只差是否为2 ,即((path[x]-path[y]-2)+3)%3==0 ;取模是怕出现负数   至于为什么用 差2 而不是差1 ,与我们每次选fy为根节点有关,见代码注释;

如果fx!=fy,则需要 维护,也就是合并集合, 并更新fx集合中所有点到 新根fy的距离,更新的过程同上: 

首先 原来 y-->fy的距离为f[y]  , x---->fx的距离为f[x],

合并之后的关系是  x---->fx------>fy<--------y;

 因为x吃y,所以distance(x,y)=2,  //ps:如果一直默认以fx为新根,dis会是1,想想为什么,和上面的判断是同一个道理

所以他们的【距离关系】可以近似看作这样的情况  x---->y----->fx---->fy;

所以得出  path[fx]=path[y]-path[x]+2; 前者指y到fy的距离,后者指,x到fx的距离;


所有path的计算都要注意如果得到负数或者大于等于3的结果要取模,本代码的实现放在里find函数中


#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
#include <queue>
#include <map>
#include <set>
#include <vector>
using namespace std;

int n;
int path[50005];
int fa[50005];
int find(int x)
{
	if (fa[x]==x)
	{path[x]=(path[x]+3)%3;	return fa[x];}
	else  
	{ 
		int fx=find(fa[x]);
		path[x]=(path[fa[x]]+path[x]+3)%3;// 更新为:自己到原先fa[x]的路径 + 新的fa[x]到根节点的路径
		return fa[x]=fx;  
	}
}
int main()
{ 
	int n,k;
	cin>>n>>k;
	int op,x,y;
	int i ;
	for (i=1;i<=n;i++)
		fa[i]=i,path[i]=0;
	
	int cun=0;
	for (i=1;i<=k;i++)
	{
		scanf("%d%d%d",&op,&x,&y);
		if (x>n||y>n)  {cun++;continue;}
		if (x==y&&op==2)  {cun++;continue;}
			int fx=find(x);
			int fy=find(y);		//找到各自的根
		if (op==1)			//同类判断
		{
		
			if (fx==fy)		//如果同根
			{
				if (path[x]!=path[y]) //那么他们与祖先的距离应该相同,
					cun++;
				continue;
			}
			else if (fx!=fy)		//如果不同类则合并
			{
				fa[fx]=fy;			//由fx指向fy;
				path[fx]=path[y]-path[x];  //由向量关系得 path[fx]=path[y]+dis(x-y)-path[x],其中dis(x-y)=0;  //注意可能为负数,因此在find函数中加了+3%3操作,也可直接在此+3%3
			}
		}
		else
		{
					//因为当x吃y,且要合并他们时,我们默认把fy作为新根,所以描述x吃y的关系用的是 path[x]=path[y]+2;//如果用fx为根,那么应该用path[x]=path[y]+1;
				if (fx==fy)		//由于上一行的关系,所以此处判断是否符合x吃y的关系式 是path[x]-path[y]!=2 而不是差1
				{
					if ((path[x]-path[y]+3)%3!=2)  //这比较必定要+3%3		
						cun++;
				}
				else
				{
					fa[fx]=fy;
					path[fx]=path[y]+2-path[x];   //注意可能为负数,因此在find函数中加了+3%3操作,也可直接在此+3%3
				}  
		}
	}
	printf("%d\n",cun);
	
	
	
	
	
	
	
	return 0;
	
}








你可能感兴趣的:(POJ-1182-食物链- 经典并查集应用)