poj1182:食物链

听说是poj中最经典的一道并查集题目。我一做,果然很经典呢!好难啊!!!真的琢磨了很久还弄懂。这道题的重点就在于怎么用并查集表示题目中的关系环。

1. 题干

原题传送门1

原题传送门2

2. 思路详解

实际上,在做这道题之前,我对并查集的了解就只停留在权重选择和压缩路径上。也就是大家司空见惯的那种模板。顺带再默写一遍复习一下:

#include <vector>
using namespace std;
class unionfind {
public:
vector<int> id, rank;
void init(int n){
id.clear(); rank.clear();
id.resize(n + 1); id.assign(n + 1, 1);
for (int i = 0; i < n + 1; i++) {id[n] = n;}
}
int find(int i){
while (id[i] != i){
id[i] = id[id[i]];
i = id[i];
}
return i;
}
void Union(int id1, int id2){
id1 = find(id1); id2 = find(id2);
if (rank[id1] > rank[id2]) {id[id2] = id1; rank[id1] += rank[id2];}
else {id[id1] = id2; rank[id2] += rank[id1];}
}
}uf;

经典的权值+压缩路径对吧?这应对模板题就够用了,模板题一套就出来了。

但是这道题不可以用这个方向去思考。这道题的rank数组不能是权重,而应该是关系约束。

2.1 题意抽象

题目的意思很简单,现在有三个物种,如果分别命名为 A,B,C,那么他们之间关系则为:A吃B,B吃C,C吃A,然后判断一个人说的话一是数据是否合法,二是是否前后矛盾。

首先要明确的是,如何用并查集代表三个物种?我们事先并没有办法给他们下一个定义,自然没有办法直接的将它们划分成对应的ABC。所以直接划分成这条路走不通。

不过其实我们可以换一个角度,从另一个视角去分析怎么划分种类。这个视角有点像是物理里的相对运动。

我们把焦点放在某个物种上。假设我是其中一个生物,在我的视角看来,我和其他的所有生物的种类的关系应该是怎么样的呢?

思考一下,不难发现,对于我来说,我面前的种类应该划分成3部分:

  1. 和我是同类的
  2. 会来吃我的种类
  3. 我会去吃的种类

不管我是A,B还是C,我总能把所有我能看到的生物分成这三个部分,而我并不需要在意自己从属于哪个种类。也就是说,如果以我自己为中心进行观察,我并不需要关心自己或者别人到底是A,B还是C,我只需要根据关系就能把其他所有物种分成3个部分。

我们回到并查集,看看并查集的特点。并查集的每一个集合,是不是选一个点作为根,其他所有的点都指向这个根节点?

发现了并查集的集合和上面题意抽象的关系吗?相当于集合里的根,其他所有的动物相当于点,都指向根,根只需要确定根和某个点关系,就能完整地把环状关系描述出来。

所以,这里的rank数组,指代的就不再是权重,而是子节点与父节点的关系。为了方便编程,我们规定,在rank中,0代表同类,1代表父节点捕食子节点,2代表子节点捕食父节点。

这么规定的好处是,如果 (rank1 + 1) % 3 == rank2 可以说明两者是捕食关系,利用了相邻的性质得出来的。

2.2 find函数的相关分析

find 函数我们当然就要考虑路径压缩。这里的路径压缩有不同的地方。因为rank数组不再代表为权重而是关系,在压缩路径的同时,子父节点的关系很有可能是会发生改变的。

我们函数的设计采用递归式设计。这样比较方便,可以只考虑子节点直接以爷爷节点为父节点时关系的变化。

我们先思考一下,子节点与爷爷节点要怎么确定?我们知道子节点与父节点的关系,知道父节点与爷爷节点的关系,是不是可以尝试用这个条件作跳板推导出子节点与爷爷节点之间的关系?

实际上呢就是可以的。我们直接将左右情况都找出来看看,实际上是有9种情况,很容易就找到了。

比如说,如果父节点与子节点的关系是同类,父节点与爷爷节点的关系是捕食,那么可以得出子节点与爷爷节点的关系是捕食。以此类推,可以推出以下表格。

0 1 2
0 0 1 2
1 1 2 0
2 2 0 1

注:列为 r1 ,表示父节点与子节点的关系,行为 r2 ,表示爷爷节点与父节点的关系。假设 r3 表示爷爷节点与子节点的关系。

如果我说根据表格的规律,可以直接推出来, $r_3 = (r_1 + r_2) mod 3 $ ,能接受吗?实际上就找个规律就出来了。

所以, find 函数就很好写了。在子节点挂到爷爷节点上之后,按上面那个公式更新一下关系数组。

2.3 union函数的相关分析

union 函数最难的地方还是在关系的更新啊~~

虽然我们可以通过 find 函数找到根节点,但是两个根节点合并的时候,我们其实是不能直接得到两个根节点的关系的,需要经过推导。自己可以举例试试,关系的更新还要推导一下的。

首先我们先分析一下,如果告诉我们X和Y的关系,他们俩关系数组要怎么更新。题目有提到,1为同类,2为A吃B,可以简单讨论一下。

type == 1 时,XY为同类,rank更新为 rx = ry = type - 1 = 0.

type == 2 时,输入为 X Y,表示X吃Y,(如果输入反过来就是Y吃X),rank更新为 rx or ry = type - 1

--> 指的是前者挂在后者上。挂完之后子节点的rank会有更新。

所以X-->Y之间的关系表达式就是 type - 1 。反过来是 (4 - type)%3

反过来求这个操作实际上就是已知X和Y是捕食,那么Y和X是被捕食,然后用算式表达式表示一下。

我们接下来考虑X和fx(X的根节点)之间关系的转换。

X-->fx 因为 rx 存的本来就是对应的关系,所以表达式为 rxfx-->X 需要推导一下,得到 (3 - rx) % 3

按照同样的推导,可以得到 Y-->fy fy-->Y 。分别是 ry (3 - ry) % 3 。重点是 fy -- > fx

我们先把它假设成 rxy 吧!

这样的话,我们可以列出一个转换式子:fy --> Y --> X == fy --> X

重点是怎么化简。还记得 find 函数推出来的结论吗?子节点挂到爷爷节点的更新关系是 (r1 + r2) % 3 ,所以 fy --> X 很自然就变成了 (d - 1 + 3 - ry) % 3

由于有fy --> X --> fx == fy --> fx 可以得到是 (d - 1 + 3 - ry + rx) % 3 ,于是更新函数就这样搞定了……

2.3 转化成符合题意的代码

最首要的条件是数据是否合法,不合法直接算说谎。题目已经指出所有不合法的情况,直接判断即可。

接下来是在同一集合的情况。

  1. 如果输入为同类但是 rank 的值不同,那就是说谎。
  2. 如果输入为捕食,如果 (rank[x] + 1) % 3 != rank[y] 说明说谎

如果不在一个集合,说明之前没有定义过,将两个合并。

最后输出即可。

3. AC代码(C++)

#include <cstdio>
#include <vector>
using namespace std;
class uf{
public:
vector<int> id, rank;
void init(int n){
id.resize(n); rank.resize(n);
for (int i = 0; i < n; i++) {id[i] = i;}
}
int find(int i){
if (id[i] == i) return i;
int t = id[i];
id[i] = find(id[i]);
rank[i] = (rank[i] + rank[t]) % 3;
return id[i];
}
void Union(int x, int y, int type){
int fx = find(x), fy = find(y);
id[fy] = fx;
rank[fy] = (3 - rank[y] + type - 1 + rank[x]) % 3;
}
}uf;
int main(){
int num = 0, k = 0, cnt = 0;
int type = 0, id1 = 0, id2 = 0;
scanf("%d%d", &num, &k);
uf.init(num + 1);
for (int i = 0; i < k; i++){
scanf("%d%d%d", &type, &id1, &id2);
if (id1 > num || id2 > num || (id1 == id2 && type == 2)) {cnt++;}
else if (uf.find(id1) == uf.find(id2)){
if (type == 1 && uf.rank[id1] != uf.rank[id2]) {cnt++;}
if (type == 2 && (uf.rank[id1] + 1) % 3 != uf.rank[id2]) {cnt++;}
}
else {uf.Union(id1, id2, type);}
}
printf("%d\n", cnt);
return 0;
}

poj1182:食物链的更多相关文章

  1. 并查集专辑 (poj1182食物链,hdu3038, poj1733, poj1984, zoj3261)

    并查集专题训练地址,注册登录了才能看到题目 并查集是一个树形的数据结构,  可以用来处理集合的问题, 也可以用来维护动态连通性,或者元素之间关系的传递(关系必须具有传递性才能有并查集来维护,因为并查集 ...

  2. POJ1182 食物链---(经典种类并查集)

    题目链接:http://poj.org/problem?id=1182   食物链 Time Limit: 1000MS   Memory Limit: 10000K Total Submission ...

  3. NOI2001|POJ1182食物链[种类并查集 向量]

    食物链 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 65430   Accepted: 19283 Description ...

  4. POJ-1182 食物链 并查集(互相关联的并查集写法)

    题目链接:https://cn.vjudge.net/problem/POJ-1182 题意 中文题目,就不写了哈哈 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃 ...

  5. poj1182食物链_并查集_挑战程序设计竞赛例题

    食物链 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 65534   Accepted: 19321 Description ...

  6. [poj1182]食物链(并查集+补集)

    食物链 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 64841   Accepted: 19077 Description ...

  7. poj1182(食物链)续

    意 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种. 有人用 ...

  8. poj1182(食物链)

    食物链 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 49320   Accepted: 14385 Description ...

  9. POJ1182 食物链(并查集)

    食物链 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 55260   Accepted: 16210 Description ...

  10. (转)poj1182食物链

    这题主要是看了http://blog.csdn.net/c0de4fun/article/details/7318642这篇解题报告,所以内容基本是转的!感谢大牛这么详细的把过程写的很清楚! 这道题目 ...

随机推荐

  1. Spring Cloud系列(三):服务消费与负载均衡

    上一篇介绍了服务提供者,有了注册中心和服务提供者,我们就可以进行服务消费了.Spring Cloud可以通过RestTemplate+Ribbon和Feign这两种方式消费服务. 我们仍然在上一篇的项 ...

  2. Spring Cloud专题之二:OpenFeign

    欢迎查看上一篇博客:SpringCloud专题之一:Eureka . OpenFeign是一种声明式的webservice客户端调用框架.你只需要声明接口和一些简单的注解,就能像使用普通的Bean一样 ...

  3. Fedora 34成哑巴了?

    原由 前几天刚更新了Fedora34,完全沉浸在Gnome40的喜悦中.但是今天用耳机听Apple Music的时候完全傻了,音量控制旋钮调了半天也没有声音,难道声卡坏了?于是,我试探性的用Parro ...

  4. 【Azure 应用服务】Azure Function集成虚拟网络,设置被同在虚拟网络中的Storage Account触发,遇见Function无法触发的问题

    一切为了安全,所有的云上资源如支持内网资源访问,则都可以加入虚拟网络 问题描述 使用Azure Function处理Storage Account中Blob 新增,更新,删除等情况.Storage A ...

  5. CS 面试题目总结(问题+答案)

    开源了一个新的github仓库CS 面试题目总结(问题+答案),主要总结一些CS大厂常见的面试问题,所有的问题与答案参考了网络上的许多博客和github仓库,也希望各位读者能够对这个仓库进行补充,毕竟 ...

  6. js更改HTML样式

    <!DOCTYPE HTML><html><head><meta http-equiv="Content-Type" content=&q ...

  7. excel判断数据是否存在另一列中

    1.if(EXACT(A2,B2)=TRUE,"相同","不同"),A2,B2相同(字母区分大小写)则函数值true正确,反馈相同,反之返回不同.注:单元格值受 ...

  8. Go基础语法0x01-数组

    数组 1.Go数组简介 数组是Go语言编程中最常用的数据结构之一.顾名思义,数组就是指一系列同一类型数据的集合.数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数组的 ...

  9. Linux 安装 git

    安装方法参考:http://www.jb51.net/os/RedHat/149653.html 具体内容: 在安装Git之前,需要先安装一些依赖包,安装依赖包之前可以先检查下是否已经安装. shel ...

  10. 暑假自学java第十一天

    1,使用java.util.Arrays类处理数组 (1 ) public static void sort(数值类型 [ ] a):对指定的数值型数组按数字升序进行排序.在数组排序中设计一个简单的冒 ...