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. C#将DataTable数据导出CSV文件

    C#将DataTable数据导出CSV文件通用方法! //导出按钮调用导出方法    protected void btnCSV_Click(object sender, EventArgs e)   ...

  2. python_pycham,连接数据库,执行sql

    本地搭建的mysql的新建的表的数据如下: 在pycham中连接mysql 执行sql  ,举例编写如下: import pymysql if __name__ == '__main__': conn ...

  3. 开发掉坑(一)tar命令解压文件覆盖源文件

    今天在编译机上编译前端代码,报了找不到依赖的异常.检查后发现是node_modules/.bin下少了一些文件. 一开始疑惑为什么本地能成功生成软链在node_modules/.bin,服务器上面却不 ...

  4. 「题解」agc031_c Differ by 1 Bit

    本文将同步发布于: 洛谷博客: csdn: 博客园: 简书: 题目 题目链接:洛谷 AT4693.AtCoder agc031_c. 题意概述 给定三个数 \(n,a,b\),求一个 \(0\sim ...

  5. Ajax(内含json)认识

    Ajax 认识 一.概念 1.Ajax 即"Asynchronous Javascript And XML"(英[eɪˈsɪŋkrənəs]异步 JavaScript 和 XML) ...

  6. Java8的Stream API确实很牛,但性能究竟如何?

    Stream Performance 已经对 Stream API 的用法鼓吹够多了,用起简洁直观,但性能到底怎么样呢?会不会有很高的性能损失?本节我们对 Stream API 的性能一探究竟. 为保 ...

  7. Unreal如何进行材质优化?

    Hello,大家好,今天给大家带来实用的材质优化,我是木偶心没.优化在每个游戏项目里面都会涉及到,是一种为了达成相同目标,寻求并采用消耗更少资源的办法.一般会在CPU,GPU,网络和内存方便进行优化. ...

  8. ClickHouse学习系列之四【副本&分片部署说明】

    背景 以前介绍过ClickHouse相关的系列文章,现在继续说明.本文开始说明ClickHouse的副本与分片,和其他数据库一样,ClickHouse也会出现单节点故障和单节点资源到达上限的情况.所以 ...

  9. jquery鼠标移入移出事件

    <!DOCTYPE html><html> <head>    <meta http-equiv="Content-type" conte ...

  10. 详解C++中的多态和虚函数

    一.将子类赋值给父类 在C++中经常会出现数据类型的转换,比如 int-float等,这种转换的前提是编译器知道如何对数据进行取舍.类其实也是一种数据类型,也可以发生数据转换,但是这种转换只有在 子类 ...