您的位置:首页 > 其它

poj1182 食物链(经典关系并查集)

2016-05-22 20:41 295 查看
http://poj.org/problem?id=1182

题意:给你n个生物k个操作,生物之间有吃、被吃、同类三种关系,1,x,y代表x和y是同类,2,x,y代表x吃y,求假话数量。

思路:并查集高级应用,直接上大牛博客。刚开始看了几个说的不怎么明白的博客被误导了,现在把思路都理清吧。

p[x]代表x的父亲节点,r[x]代表x与p[x]的关系,x、y所在集合的代表元素rx、ry。

由于只有三种关系,所以我们命r[x]=0代表p[x]与x同类,r[x]=1代表p[x]吃x,r[x]=2代表x吃p[x]。而因为题中给出的操作d=1为同类,d=2为正向吃,正好与这里匹配,所以这里的数字代表权值,偏移量为1,途中的权值一般也就是d-1了。

Find操作:这是一个重点,找x所在集合的根节点(代表元素)。上述大牛博客中给出了推导思想,这里我感觉说的没人家好,仅仅记录下自己一些额外的理解。首先公式,r[ x ] = ( r[ x ] + r[  tmp ] )%3。这个公式有些地方说是找规律,事实上数学推导也行,不过没找规律快捷,这里有找规律的过程博客2。针对这个式子,他是根据爷爷父亲和父亲儿子的关系推导出爷爷孙子的关系,这里刚开始我没有理解。后来想想,这里递归查询根节点时,是一步一步往深递归,找到的根节点即为爷爷,返回上一层后pre[x]即为父亲,这里直接求出的爷孙关系r[x]就是当前递归层经历节点和根节点的关系啊!然后退回,中间传递根节点值,退回的节点再次成为了根节点的孙子。话句话说,这相当于从根开始,由深到浅往下拆树啊!!这思想精辟啊,理解了这点和公式,就不难了。

Union操作:将两个根节点不相同的根节点合并。思想刚开始的博客说清了,就是向量转移,根节点一变那关系也随之改变,只需改变根节点的关系即可。表达式:r[ rx ] = (3 - r[ x ] + d + r[ y ])%3。根据下图理解:(注意这里d改成d-1)



判断是否为假话,除了两种不需要通过并查集判断的情况外,都要检查他们的偏移量才可确定真假。而需要检查真假的肯定都在一个集合内了,所以不需要Union操作。这样的话只剩推导出公式,检验是否相等即可,公式性质和上图一样。(r[ x ] + 3 - r[ y ])%3 != d-1,即为假。



#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>

using namespace std;

typedef long long LL;

const int N = 100010;
const int INF = 0x3f3f3f3f;

int pre
, sum, r
;

int Find(int x)
{
if(x == pre[x]) return x;
int tmp = pre[x];
pre[x] = Find(pre[x]);
r[x] = (r[x]+r[tmp])%3;
return pre[x];
}

void Union(int x, int y, int d)
{
int rx = Find(x);
int ry = Find(y);
pre[rx] = ry;
r[rx] = (3-r[x]+d-1+r[y])%3;
}

int main()
{
// freopen("in.txt", "r", stdin);
int n, k, d, x, y;
sum = 0;//假话数
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i++)
{
pre[i] = i;
r[i] = 0;
}
while(k--)
{
scanf("%d%d%d", &d, &x, &y);
if(x > n || y > n)
{
sum++;
continue;
}
if(d == 2 && x == y)
{
sum++;
continue;
}
int rx = Find(x);
int ry = Find(y);
if(rx == ry)
{
if((r[x]+3-r[y])%3 != d-1) sum++;
}
else
{
Union(x, y, d);
}
}
printf("%d\n", sum);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  poj