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

本文介绍了一道关于食物链关系的经典并查集问题,通过解析题意、梳理解题思路,给出了完整的AC代码实现。重点在于理解并查集的getf和merge函数中的权值变化规律。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动物王国中有三类动物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.youkuaiyun.com/j_sure/article/details/25917915

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

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

 

vfa
000
011
101
022
202
112
120
210
221

 

由这个关系表我们可以推得一个式子: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

t1u(v)t2
000
012
101
021
202
110
122
211
220

思路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;
}

 

 

### 并查集算法的时间复杂度分析 并查集是一种高效的用于处理集合合并与查询的算法。在POJ 1182 食物链问题中,使用了并查集来判断动物之间的关系,并且通过路径压缩和按秩合并等优化手段,可以极大地提高算法的效率。 #### 路径压缩的影响 路径压缩是并查集中一种重要的优化技术,它能够将查找过程中经过的所有节点直接连接到根节点上。这种操作使得后续查找的时间复杂度接近于常数[^1]。具体来说,路径压缩后的查找操作时间复杂度可以用阿克曼函数的反函数 \( \alpha(n) \) 来表示,其中 \( n \) 是集合中的元素个数。阿克曼函数的增长速度极慢,因此 \( \alpha(n) \) 在实际应用中几乎可以视为常数。 ```python def Find(x): if x != par[x]: par[x] = Find(par[x]) # 路径压缩 return par[x] ``` #### 按秩合并的作用 按秩合并是一种优化策略,它通过将较小的树合并到较大的树上来减少树的高度。这种方法结合路径压缩后,可以进一步降低操作的时间复杂度[^2]。在实际实现中,可以通过维护一个数组 `rank` 来记录每个集合的深度,并在合并时选择深度较小的树挂接到深度较大的树上。 ```python def Union(x, y): rootX = Find(x) rootY = Find(y) if rootX != rootY: if rank[rootX] > rank[rootY]: par[rootY] = rootX elif rank[rootX] < rank[rootY]: par[rootX] = rootY else: par[rootY] = rootX rank[rootX] += 1 ``` #### 时间复杂度总结 对于 POJ 1182 食物链问题,假设总共有 \( n \) 个动物和 \( m \) 条关系,则初始化并查集的时间复杂度为 \( O(n) \),每次查找或合并操作的时间复杂度为 \( O(\alpha(n)) \)[^2]。由于 \( \alpha(n) \) 的增长极其缓慢,在实际情况下可以认为其为常数。因此,整个算法的时间复杂度主要由关系数量 \( m \) 决定,最终的时间复杂度为 \( O(m \cdot \alpha(n)) \)[^1]。 ### 代码示例 以下是一个完整的并查集实现,适用于 POJ 1182 食物链问题: ```python class UnionFind: def __init__(self, n): self.par = list(range(3 * n)) self.rank = [0] * (3 * n) def Find(self, x): if self.par[x] != x: self.par[x] = self.Find(self.par[x]) return self.par[x] def Union(self, x, y): rootX = self.Find(x) rootY = self.Find(y) if rootX != rootY: if self.rank[rootX] > self.rank[rootY]: self.par[rootY] = rootX elif self.rank[rootX] < self.rank[rootY]: self.par[rootX] = rootY else: self.par[rootY] = rootX self.rank[rootX] += 1 def Same(self, x, y): return self.Find(x) == self.Find(y) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值