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这篇解题报告,所以内容基本是转的!感谢大牛这么详细的把过程写的很清楚! 这道题目 ...
随机推荐
- fiddler选项卡-Statistc(统计)
Statistc Statistc是fiddler用来对session列表里的Session相关情况的统计,利用这个选项,可以对请求进行性能以及其他数据分析 1.界面 2.参数详解 建议:打开fidd ...
- javaBean命名规范 get / set 后的首字母大写
javaBean命名规范 Sun 推荐的命名规范 1 ,类名要首字母大写,后面的单词首字母大写 2 ,方法名的第一个单词小写,后面的单词首字母大写 3 ,变量名的第一个单词小写,后面的单词首字母大写 ...
- NoSQL:如何使用NoSQL架构构建实时广告系统
JDNoSQL平台是什么 JDNoSQL平台是一个分布式面向列的KeyValue毫秒级存储服务,存储结构化数据和非机构化数据,支持随机读写与更新,灵活的动态列机制,架构上支持水平扩容,提供高并发.低延 ...
- STP协议与MSTP协议
STP协议与MSTP协议 目录 一.STP概述 1.1.交换网络环路的产生 1.2.STP简介 二.生成树算法 2.1.生成树算法的步骤 2.2.网桥 ID 2.3.选择根端口 2.4.选择指定端口 ...
- 35、sudo权限设置
提示:sudo的介绍在"13.linux中用户和用户组"中有详细介绍: (1)简历里要加上如下项目经验: 服务器用户权限管理改造方案与实施项目 日期: 在了解公司业务流程后,提出权 ...
- 【luogu P3807】【模板】卢卡斯定理/Lucas 定理(含 Lucas 定理证明)
[模板]卢卡斯定理/Lucas 定理 题目链接:luogu P3807 题目大意 求 C(n,n+m)%p 的值. p 保证是质数. 思路 Lucas 定理内容 对于非负整数 \(n\),\(m\), ...
- noi 162 post office dp
大致题意: 有v个村庄,每个村庄有各自的位置,且每个位置互不相同.现在要在村庄上设立P个邮局,使每个村庄到最近的邮局的距离之和最小. 分析: 定义状态d[i][j]表示前i个村庄,在这i个村庄中设立j ...
- 编译aarch64 Linux内核并基于qemu运行
核心流程 首先,本文主要讲述如何编译Linux内核并在qemu虚拟机上运行.这里针对的架构是aarch64. 本文的实验平台是Ubuntu 16.04. 为了达成目标,我们需要有qemu.buildr ...
- Centos 6.5 GitLab安装配置搭建详细过程
GitLab搭建详细过程 一.前提 系统:Centos 6.5 软件版本:gitlab-7.8.4 Selinux:关闭 防火墙规则:先清空(搭建好了后续自己添加相关放行规则) 二.yum源配置和 ...
- 『无为则无心』Python函数 — 25、Python中的函数
目录 1.函数的使用 (1)定义函数 (2)调用函数 (3)使用函数的注意事项 2.函数的参数 3.实参的类型 Python函数的说明: Python中函数的应用非常广泛,前面章节中我们已经接触过多个 ...