poj1182:食物链
poj1182:食物链
听说是poj中最经典的一道并查集题目。我一做,果然很经典呢!好难啊!!!真的琢磨了很久还弄懂。这道题的重点就在于怎么用并查集表示题目中的关系环。
1. 题干
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部分:
- 和我是同类的
- 会来吃我的种类
- 我会去吃的种类
不管我是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 存的本来就是对应的关系,所以表达式为 rx 。fx-->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 转化成符合题意的代码
最首要的条件是数据是否合法,不合法直接算说谎。题目已经指出所有不合法的情况,直接判断即可。
接下来是在同一集合的情况。
- 如果输入为同类但是
rank的值不同,那就是说谎。 - 如果输入为捕食,如果
(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:食物链的更多相关文章
- 并查集专辑 (poj1182食物链,hdu3038, poj1733, poj1984, zoj3261)
并查集专题训练地址,注册登录了才能看到题目 并查集是一个树形的数据结构, 可以用来处理集合的问题, 也可以用来维护动态连通性,或者元素之间关系的传递(关系必须具有传递性才能有并查集来维护,因为并查集 ...
- POJ1182 食物链---(经典种类并查集)
题目链接:http://poj.org/problem?id=1182 食物链 Time Limit: 1000MS Memory Limit: 10000K Total Submission ...
- NOI2001|POJ1182食物链[种类并查集 向量]
食物链 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 65430 Accepted: 19283 Description ...
- POJ-1182 食物链 并查集(互相关联的并查集写法)
题目链接:https://cn.vjudge.net/problem/POJ-1182 题意 中文题目,就不写了哈哈 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃 ...
- poj1182食物链_并查集_挑战程序设计竞赛例题
食物链 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 65534 Accepted: 19321 Description ...
- [poj1182]食物链(并查集+补集)
食物链 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 64841 Accepted: 19077 Description ...
- poj1182(食物链)续
意 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A. 现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种. 有人用 ...
- poj1182(食物链)
食物链 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 49320 Accepted: 14385 Description ...
- POJ1182 食物链(并查集)
食物链 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 55260 Accepted: 16210 Description ...
- (转)poj1182食物链
这题主要是看了http://blog.csdn.net/c0de4fun/article/details/7318642这篇解题报告,所以内容基本是转的!感谢大牛这么详细的把过程写的很清楚! 这道题目 ...
随机推荐
- Httprunner的使用
一.httprunner的简介 HttpRunner 是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份 YAML/JSON 脚本,即可实现自动化测试.性能测试.线上监控.持续集成等多种 ...
- JAVA并发(7)-并发队列PriorityBlockingQueue的源码分析
本文讲PriorityBlockingQueue(优先阻塞队列) 1. 介绍 一个无界的具有优先级的阻塞队列,使用跟PriorityQueue相同的顺序规则,默认顺序是自然顺序(从小到大).若传入的对 ...
- You Only Look One-level Feature
你只需要看一个层次的特征 摘要:本文回顾了单阶段检测器的特征金字塔网络(FPN),指出FPN的成功在于其对目标检测优化问题的分治解决,而不是多尺度特征融合.从优化的角度来看,我们引入了一种替代的方法来 ...
- Java源码详解系列(十二)--Eureka的使用和源码
eureka 是由 Netflix 团队开发的针对中间层服务的负载均衡器,在微服务项目中被广泛使用.相比 SLB.ALB 等负载均衡器,eureka 的服务注册是无状态的,扩展起来非常方便. 在这个系 ...
- C#中怎样使控件随着窗体一起变化大小
此文原作者为CSDN的 zhouwen5288,原文链接 http://blog.csdn.net/zhouwen5288/article/details/6493835 本人仅作为随笔备忘,转载请 ...
- 用python+pyqt5语言编写的扫雷小游戏软件
github源码地址:https://github.com/richenyunqi/Mine-game ,撒娇打滚求star哦~~ღ( ´・ᴗ・` )比心 扫雷主界面模块 整个扫雷界面使用大量的白色方 ...
- 四、JavaSE语言基础之运算符
什么是是运算符 运算符:用于数据运算的符号,运算是一种处理.(注:浮点型数据(float.double)进行运算会出现精度丢失的情况) 运算符大致可分为以下六种: 一.算术运算符:+.-.*./.%. ...
- mybatis_plus实现自动填充和逻辑删除
自定义填充 设置自定义填充规则 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.i ...
- 10.8、mysql日志
mysql生成或相关联的日志文件种类繁多,这里重点关注与mysql数据库服务相关 的几类日志文件: 1.错误日志: 记录mysql服务进程mysql的在启动/关闭/运行过程中遇到的错误信息: [mys ...
- DRF之过滤排序分页异常处理
一.过滤 对于列表数据要通过字段来进行过滤,就需要添加 django-filter 模块 使用方法: # 1.注册,在app中注册 settings.py INSTALLED_APPS = [ 'dj ...