解题思路:并查集经典中的经典题,在网上看了很多大牛的思路,大部分是增加一个结构体存动物间的关系,结合并查集判断,但是关系域的更新比较复杂,一下子不太容易理解。所以就有人另开思路,这里介绍一个十分巧妙的思路。

一般我们都会把一个动物当成一个节点,然后去执行并查集等操作。但是有位大牛另辟蹊径,给每个动物赋予三个节点(n,2n,3n),这样就将并查集的节点数量扩展到3n,用并查集维护这3n个节点的信息就行。下面是具体的思路和操作: 
1 先来考虑什么情况下a和b两个动物是同类: 
(1)a吃c,b也吃c,则a和b同类; 
(2)a吃c,c吃d,d吃b,则a和b同类; 

2 有了上面的基础,我们还需要巧妙的定义下面的规则: 
(1)如果a和b是同类,则a的三个节点(a_n,a_2n,a_3n)分别指向b的对应的三个节点(b_n,b_2n,b_3n),即a_n -> b_n , a_2n -> b_2n , a_3n -> b_3n; 

POJ 1182 食物链

(2)如果a吃b,则a的三个节点(a_n,a_2n,a_3n)分别指向对应b的三个节点(b_n,b_2n,b_3n)的下一个节点,即a_n -> b_2n , a_2n -> b_3n , a_3n -> b_n;

 POJ 1182 食物链

 

3 所以我们现在可以表示1中提到的两种同类的情况了: 
(1) 

POJ 1182 食物链

(2) 

POJ 1182 食物链

按照上面的规则对每一个动物去建立关系,那么,如果a和b是同类,肯定有a_n与b_n连通,即有相同的根节点;如果a吃b,肯定有a_n与b_2n连通;如果b吃a,肯定有b_n与a_2n连通。

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 300010;
int pre[maxn];
void Init()
{
	for(int i = 0;i < maxn; i++)
		pre[i] = i;
} 
int find(int x)
{
	while(x != pre[x])
	{
		x = pre[x];
	}
	return x;
}
void merge(int x,int y)
{
	int a = find(x);
	int b = find(y);
	if(a != b)
		pre[b] = a;
}
int main()
{
	int n,k,d,x,y,flag = 0;
	Init();
	scanf("%d %d",&n,&k);
	for(int i = 0;i < k; i++)
	{
		scanf("%d %d %d",&d,&x,&y);
		if(x > n || y > n || x < 1 || y < 1 || (d == 2 && x == y))
		{
			flag++;
			continue;
		}
		if(d == 1)
		{
			if(find(x) == find(y + n) || find(y) == find(x + n))
			{
				flag++;
				continue;
			}
			else
			{
				merge(x,y);
				merge(x + n,y + n);
				merge(x + 2 * n,y + 2 * n);
				continue;
			}
		}
		if(d == 2)
		{
			if(find(x) == find(y) || find(y) == find(x + n))
			{
				flag++;
				continue;
			}
			else
			{
				merge(x,y + n);
				merge(x + n,y + 2 * n);
				merge(x + 2 * n,y);
			}
		}
	}
	printf("%d\n",flag);
	return 0;
}


大牛原文链接:https://blog.csdn.net/lisong_jerry/article/details/80029967 

相关文章: