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. 如何使用Nsight Compute?

    如何使用Nsight Compute? 下图command Line Argunments是指训练或测试命令,Linux下直接用测试或训练命令

  2. Java如何使用while和for嵌套循环控制输出数据,使数据奇偶行不同

    /* 题目1 使用Eclipse编写控制台应用程, 使用while循环在控制台打印10行10列的如下图形 □ □ □ □ □ □ □ □ □ □ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ □ □ □ □ ...

  3. 【NX二次开发】获取面的类型 UF_MODL_ask_face_type

    源码: extern DllExport void ufsta(char *param, int *returnCode, int rlen) { UF_initialize(); int face_ ...

  4. 使用 Hexo 搭建静态博客

    目录 Hexo 简介 什么是 Hexo? Hexo 安装 Hexo 建站 Hexo 配置 Hexo 自定义主题 Hexo 写作 Hexo 服务器 Hexo 生成与发布 一键部署 Hexo 站点到 gi ...

  5. 复习Spring第三课--数据源配置的多种方式

    spring数据源配置可以说分为:spring容器自带连接池.项目中创建连接池.服务器创建连接池三种 一.spring容器自带连接池   Spring本身也提供了一个简单的数据源实现类DriverMa ...

  6. Pytest学习笔记6-自定义标记mark

    前言 在pytest中,我们可以使用mark进行用例的自定义标记,通过不同的标记实现不同的运行策略 比如我们可以标记哪些用例是生产环境执行的,哪些用例是测试环境执行的,在运行代码的时候指定对应的mar ...

  7. 看完互联网大佬的「LeetCode 刷题手册」, 手撕了 400 道 Leetcode 算法题

    大家好,我是 程序员小熊 ,来自 大厂 的程序猿.相信绝大部分程序猿都有一个进大厂的梦想,但相较于以前,目前大厂的面试,只要是研发相关岗位,算法题基本少不了,所以现在很多人都会去刷 Leetcode ...

  8. 怎样用好PS中的钢笔工具(附练习钢笔工具网站)

    想要在PS中得心应手的的描绘出自己想要的线条(也就是路径),就需要对[钢笔工具]有一个充分的理解. [钢笔工具]绘出来的线条全部都是贝赛尔曲线,所以你在学习[钢笔工具]之前,要补习一下贝赛尔曲线的常识 ...

  9. 创建Cloudflare CDN

    背景说明: XX全球版项目CDN, 原有改之前:主CDN为Akamai,备CDN为Cloudflare. 计划改之后:主CDN为Cloudflare,备CDN为Akamai. 原因:Akamai CD ...

  10. 98、配置ftp服务器(vsftpd)

    98.1.安装vsftpd: 1.安装: [root@m01 ~]# yum install -y vsftpd #安装vsftpd [root@m01 ~]# vsftpd -v #查看ftp的版本 ...