NYOJ207 VS POJ1182 食物链

43 篇文章 0 订阅
3 篇文章 0 订阅
题目链接: http://acm.nyist.net/JudgeOnline/problem.php?pid=207
                     http://poj.org/problem?id=1182
题目分析:
首先这道题是用 带权的并查集来做,其实这个我自己已经想到了。不过思路很不清晰。然后看了别人的一些处理技巧,很不错,在这里分析一下。当然我会尽力的把分析写得详细一些,因为可能日后我自己也需要回头来看看的,好了,废话不多说了。对于这里面的关系的递推处理,网上都没有一个清晰的解释。所以大家如果觉得好的话,希望转载的时候能注明出处,将是我以后将算法写得更清楚详细的动力: http://blog.csdn.net/kay_zhyu/article/details/8863485
在说之前,首先要明确这道题的 大体思想
1、如果两个物种有联系,不管是吃,被吃还是同类,它们之间应该是有一条路径可达的,也就是它们在一个集合中。
2、如果a,b有关系,b,c有关系,那么a,c之间的关系式可以通过两者的关系推出来的。
好,下面围绕着上面的两个思想来一一分析。
首先就是 怎么把有关系的物种放到同一个集合中去。这就需要用到并查集了。每一次输入d,x,y,也就是相当于x,y之间有一条权为d的路径。先忽略这个权值,直接考虑路径,那并查集的路径建立就不用我说了。一个parent数组,parent[i]表示从parent[i]到i有一条路径。OK,那不同的食物圈就构成了一个连通区域。每一个连通区域都有一个根节点。
下面考虑怎么处理这个权。先说点数学的东西,任何一种偏序关系都满足自反、对称、传递。
自反:自己跟自己满足偏序关系。
对称:a,b的偏序关系为r,则b,a的偏序关系为~r.表示求反。
传递:a,b的偏序关系为r1,b,c的偏序关系为r2,a,c的偏序关系为r1+r2.这里的+表示一种直和吧,符号打不出来,一个圆圈里面有一个加号。
为了方便,用一个relation数组来维护这个权值。relation[i]表示的是i所在的连通区域的根节点到i的关系。先忽略这个关系数组的维护过程,把整体的思路理清楚。如果有两个物种加进来,就有两种情况,要么它们在同一个连通集里面。要么不在同一个连通集里面。
两者在同一个连通集里面
1、新加的关系表明x,y是同类,那么它们两个分别到连通区域根节点的关系应该是一样的,要不就矛盾了。(记为case1)
2、如果新加的关系表明x,y不是同类,那么在加入y以后,x相对于根节点的关系和x原本相对于根节点的关系应该是不变的,否则就矛盾了。(记为case2)
两者在不同的连通集里面,就直接连接两个连通集就可以了。(记为case3)
路径缩短处理
由于后来物种会越来越多,我们不希望食物链拉的很长,所以会尽可能的让所有的节点都直接和根节点相连。这样整个连通的图就有点呈现出星形。
怎么维护关系数组。数组里面的每个元素的取值要么是0(同类),要么是1(父吃子),要么是2(子吃父)。至于为什么要这么设置,参考另一篇博客 http://blog.csdn.net/c0de4fun/article/details/7318642,这里我不想废话,我只想理清怎么搞这个关系递推。假设前面的数据我已经处理好了,现在要处理d,x,y.为了叙述的方便,记relation[x]为x根->x.那么现在就有三种情况:
case1:这种情况x根与y根相同。如果x根->x与y根->y不同,表明x,y不是同类,与d=1矛盾。
case2:这种情况x根与y根相同。如果加入y之后,x根->x=x根(即y根)->y+y->x,如果新求出来的关系与本身已有的x根->x的关系不同,则矛盾。
case3:这种情况x根与y根不同。由于这里添加的是x到y的一条有向边。将y根的父节点设置为x根,更新y根父节点到x根的关系,即x根->y根=x根->x+x->y+y->y根,由于这里都是有向边,所以更新关系的时候注意关系的方向。这里需要注意,我们只更新了两个根之间的关系,x根与原来的y所在的连通区域里面的节点的关系都没有更新,这就是为什么要在一开始判断之前就要调用Find函数,更新每个节点到其根节点的关系。
初始条件:有了这个递推,就好办了。初始条件parent就是并查集一般的初始条件,父节点等于自己。由于初始的时候父节点时自己,当然自己跟自己的关系肯定是同类咯,也就是relation[i]=0
当然思路还有点不清楚也没关系,代码中也给出了相应的注释。希望能更好的理解问题。
#include<stdio.h>
const int N = 50005;
int parent[N];
int relation[N];//根节点到节点的关系
void Init(int n)
{
	for(int i = 0; i <= n; ++i)
	{
		parent[i]= i;
		relation[i] = 0;
	}
}
//更新的步骤,先将当前节点与其根节点相连,然后更新其与根节点的关系
//当前节点x与根节点r的关系更新的方式:(x与其父节点的关系+其父节点的关系与根节点的关系)%3
//所以在更新节点x的数据之前需要更新其父节点的数据,这是Find为什么搞成递归函数的原因
//其更新的顺序是从根节点一直往下,一直到当前节点x的父节点。
int Find(int x)
{
	if(x != parent[x])//不是根节点
	{
		int temp = parent[x];
		//将当前节点的父节点设置为根节点
		parent[x] = Find(temp);
		//更新当前节点与根节点的关系,由x->x父和x父->父根的关系得到x->父根的关系
		//所以在这之前必须更新其父节点与根节点的关系
		relation[x] = (relation[x] + relation[temp]) % 3;
	}
	return parent[x];
}

int main()
{
	int n,m,i;
	int x,y,d;
	int rx,ry;
	int cnt;
	while(scanf("%d %d", &n, &m) != EOF)//POJ上只需要一次输入,所以不需要while循环
	{
		cnt = 0;
		Init(n);
		for(i = 0; i < m; ++i)
		{
			scanf("%d %d %d", &d, &x, &y);
			if(x > n || y > n)
			{
				++cnt;
				continue;
			}
			if(d == 2 && x == y)
			{
				++cnt;
				continue;
			}
			rx = Find(x);
			ry = Find(y);
			if(rx == ry)//属于同一个子集
			{
				//如果x、y是同类,那么他们相对于根节点的关系应该是一样的
				//如果不是同类,加入y之后,x相对于根节点的关系(x根->y,y->x(即3-(d-1)=2).即x根->x)应该是不变的
				if((d == 1 && relation[x] != relation[y]) ||
					(d == 2 && relation[x] != (relation[y] + 2)%3))
					++cnt;
			}
			else//合并两个连通区域
			{
				parent[ry] = rx;//y根的父节点更新成x根
				//(d - 1)为x与y的关系,3-relation[y]是y与y的根节点的关系,注意方向,relation[x]是其根节点与x的关系
				//x根->x,x->y,y->y根:即x根->y根
				relation[ry] = (relation[x] + d - 1 + 3 - relation[y]) % 3;
			}
		}
		printf("%d\n", cnt);
	}
	return 0;
}




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值