【poj-1182】 食物链(种类并查集)

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

解题报告 :

经典的种类并查集,有三种关系。这种题只要找到那几个关系的式子,其他的就比较好写了,一开始自己写了一个,但一直错,也不知道哪里的问题,最后看了大佬们的博客,倒推回去,发现是merge函数中应该是用原来的节点u,v来推导w[t2],而不是根节点t1,t2来推导。这种推导关系一定要注意弄清楚。

我的代码的关系可能会比较乱,如果没看懂,可以到这个博客看看:       https://blog.csdn.net/j_sure/article/details/25917915

接下来是我自己的代码的思路:

首先是getf函数:v为当前节点,f为v的父节点,a为求得的结果。经过递归后,f的父节点已经变成根节点,其权值w也变成与根节点的关系,因此我们可以用v和f两点,来求v点的权值(即a)。以下为关系表:

v f a
0 0 0
0 1 1
1 0 1
0 2 2
2 0 2
1 1 2
1 2 0
2 1 0
2 2 1

由这个关系表我们可以推得一个式子:w[a]=(w[v]+w[f])%3这个式子就是getf中权值变化的式子。

接下来是merge函数中的表达式:

merge函数中是合并两个不在同一集合的两个点,而由题目已知这两个点有两种关系,所以我们可以分开讨论。

设两个节点u,v,他们的根节点是t1,t2。合并之后是f[t2]=t1,因此我们要求w[t2]的关系。

d=1时:

思路1:把关系写出来,找适合的式子。最后发现这样一个规律。w[t2]=(w[u]+w[v]*2)%3

t1 u(v) t2
0 0 0
0 1 2
1 0 1
0 2 1
2 0 2
1 1 0
1 2 2
2 1 1
2 2 0

思路2:因为u、v关系相同,所以我们可以把u、v看作同一个点,然后我们把v当作中间节点,t1当作父节点,t2当作v的子节点,按照getf中的求法来求。

首先我们先把v当作t2的父节点,此时w[t2]=(3-w[v])%3(写一下两个节点的关系表,就可以很明显的看出来),然后按照getf中寻找父节点的关系来求出t2与t1的关系(即把w[t2]当作w[f]带入)。

d=2时:

这个关系的找法和d=1时的思路二时一样的,只不过多了一层中间节点而已。当然找节点的顺序不同,得出来的表达式也就会略有区别。

最后是判断给的关系是不是合理的:已知输入是 t1,t2则t1是吃t2的,若t2的父节点是t1,则w[t2]=1。t1和t2都与根节点直接相连,因此我们可以把t1当作根节点,原来根节点当作中间节点,来连接起t1与t2。此时中间节点的对与t1的权值为:(3-w[t1])%3。w[t1]为原来t1的权值。再根据getf中的式子我们可以得出最后t2的权值表达式为:(w[t2]+(3-w[t1])%3)%3。因此只要判断这个式子和1是否相等即可。

ac代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring> 
#include<queue>
#include<stack> 
#include<string.h>
using namespace std;
int n,k;
int f[50009];
int w[50009];//0同类,1被父节点吃,2吃父节点 
void init()
{
	for(int i=0;i<=n;i++)
	{
		f[i]=i;
		w[i]=0; 
	}
}
int getf(int v)
{
	if(f[v]!=v)
	{ 
		int temp=f[v];
		f[v]=getf(f[v]);
		w[v]=(w[v]+w[temp])%3;
	} 
	return f[v];
}
void merge(int u,int v,int d)
{
	int t1,t2;
	t1=getf(u);
	t2=getf(v);
	if(t1!=t2)
	{
		if(d==1)
		{
			w[t2]=(w[u]+w[v]*2)%3;//(w[u]+(3-w[v])%3)%3 这种写法也对,两者是找的思路不同
		}
		else if (d==2)
		{
			int temp=w[v];
			w[t2]=((3-w[v])%3+(w[u]+1)%3)%3;
        //注意这里是u,v不是t1、t2。是找原来节点而不是根节点的关系 
        //w[t2]=(w[u]+(1+(3-w[v])%3)%3)%3;这样写也可以,两种方法思路一样,不过节点找的顺序不同
		}
		f[t2]=t1;		
	}
}
int join(int u,int v)
{
	int t1,t2;
	t1=getf(u);
	t2=getf(v);
	if(t1==t2)return 0;
	else return 1;
}
int main()
{
	int ans=0;
	scanf("%d%d",&n,&k);
	init();
	for(int i=0;i<k;i++)
	{
		int  d,x,y;
		scanf("%d%d%d",&d,&x,&y);
		if(x>n||y>n)ans++;
		else if(d==1)
		{
			if(!join(x,y))
			{
				if(w[x]!=w[y])
				ans++;
			}
			else 
			merge(x,y,1);
		}
		else
		{
			if(x==y)ans++;
			else if(!join(x,y))
			{
				if((w[y]+(3-w[x])%3)%3!=1)
				ans++;
			}
			else
			{
				merge(x,y,2);				
			}
			
		}
	}
	printf("%d\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/QQ_774682/article/details/81126457