4000 对于POJ 1182 食物链 问题的详细分析加代码
2017-08-17 14:47
225 查看
题目:http://poj.org/problem?id=1182
指导博客:http://blog.csdn.net/c0de4fun/article/details/7318642
1、题目展示
Description
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是”1 X Y”,表示X和Y是同类。
第二种说法是”2 X Y”,表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
Input
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
Output
只有一个整数,表示假话的数目。
Sample Input
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
Sample Output
3
2、题目分析
(1)首先,这是使用并查集来解决的,没有毛病。所以并查集的三个模块都要有,哪三个模块,不用说吧!!
(2)其次,虽然这是一个并查集,但是它的find 和 union 两个模块需要进行一定的修改,但是find中仍然包括 查找 和 路径压缩。至于怎么压缩和修改,下面解释。
(3)题目中说过 A吃B,B吃C,C吃A;
那么我们需要定义一些状态来表示这些。
我们需要表示什么呢,就上述那样,但是我们并不清楚谁是A,B,C,所以不能这么定义。
那么我们需要定义什么状态?
当然是表示 同族状态,吃状态,被吃状态。
这是什么意思?由于这是一个并查集,那么我们只要表示儿子和父亲节点的关
系即可,这样我们就可以根据他们的关系来判断谁是假话,谁是真话。
那么我们就定义:
const int same = 0; //我们设置 0 来表示儿子节点 和 父亲结点同族 的关系
const int be_ate = 1; //我们设置 1 来表示儿子结点 被 父亲节点吃 的关系
const int eat = 2; //我们设置 2 来表示儿子结点 吃 父亲节点 的关系
至于我们为什么只设置 0, 1, 2 这三个数字,而不是设置 4 5 6 这些数字,后面我们继续摆
A吃B,B吃C,C吃A 那么这是一个循环,所以我们需要创建一个循环,只有三个状态的循环,那么我们如何进行循环,在我们创建循环队列的时候,
我们是使用的 求余% 来进行解决了,这里我们同样可以使用 求余% 来解决
这就是为什么我们需要选择 0、1、2这三个数来表示状态,只是为了状态的循环
那我们如何来判断儿子同爷爷的关系呢?
我们枚举一下儿子与父亲,父亲与爷爷,儿子与爷爷的关系的关系
儿子与父亲 父亲与爷爷 儿子与爷爷
0 0 0
0 1 1
0 2 2
1 0 1
1 1 2
1 2 0
2 0 2
2 1 0
2 2 1
通过这个枚举我们也可以看出,儿子与爷爷的关系 满足 儿子与爷爷 =(儿子与父亲 + 父亲与爷爷)% 3;
这个关系会运用在哪?当然是压缩路径的时候,如果路径被压缩了,那么我们就要计算儿子与根节点的关系
这个刚刚好,为啥?看代码!!!
(4)上面是find函数,那么接下来就是union函数的,也就是 集合与集合之间的关系
这个函数的意义在于,将所有的集合合并为一个集合,然后我们就可以看到每个元素与根节点的关系,然后我们
就可以判断出 x与y 关系了。
在这里我们设置一下两个集合的根节点为 root_x 和 root_y, 我们设置 x 和 y 分别为这两个集合中的元素
根据并查集的原理,我们晓得压缩路径后的集合高度最多只有两层,所以我们认为
x 相对于 root_x 的关系 为 x->status;
y 相对于 root_y 的关系 为 y->status;
y 相对于 x 的关系 为 d - 1;
那么 我们现在要获得 root_y 相对于 y 的关系
根据相对的原理来说,他们的关系我们先取 负数, 即 -(y->status)
但是 -(y->stast)这是负数,我们并没有这个关系,但是我们可以加 3
由于这是一个循环,加3对于这个循环来说没有太大关系,那么我们就可以得到
root_y 相对于 y 的关系 为 3 - (y->status);
好了,准备工作做完了。我们可以来计算 root_y 相对于 root_x 的关系了
我们设 root_y 相对于 root_x 的关系为 root_y -> status
root_y 相对于 root_x 的关系 = (root_y 相对于 y 的关系 + y 相对于 x 的关系 + x 相对于 root_x 的关系) % 3;
即 root_y -> status = ((3 - y->status) + (d - 1) + x->status) % 3;
然后具体的实现………………………………(不会看代码吗 ^_^)
(5)好了,终于要到最后一部分了,如何判断他们是不是假话
对于这个并查集来说,判断是不是假话,如果不在一个集合,我们只有超出限制的才是假话。
对于同一个集合的,我们就需要判断一下他们同root结点的关系了,
如果他们是同一种类的,他们同root结点的关系一定是一样的
如果他们是相互想吃的,这个我们就简单了,根据相对来说,我们很容易得到
最后我们可以总结出一个判断的公式
d - 1 != (3 - x->status + y->status) % 3 如果满足这个,都是假话。
所以到此,这个题我们都分析完了,接下来就是代码部分
当然笔主代码跟指导的博客主的代码差不多,最后分析出来的公式本人没有实验过,具体自己可以实验一下.
3、代码实现
指导博客:http://blog.csdn.net/c0de4fun/article/details/7318642
1、题目展示
Description
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是”1 X Y”,表示X和Y是同类。
第二种说法是”2 X Y”,表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
Input
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
Output
只有一个整数,表示假话的数目。
Sample Input
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
Sample Output
3
2、题目分析
(1)首先,这是使用并查集来解决的,没有毛病。所以并查集的三个模块都要有,哪三个模块,不用说吧!!
(2)其次,虽然这是一个并查集,但是它的find 和 union 两个模块需要进行一定的修改,但是find中仍然包括 查找 和 路径压缩。至于怎么压缩和修改,下面解释。
(3)题目中说过 A吃B,B吃C,C吃A;
那么我们需要定义一些状态来表示这些。
我们需要表示什么呢,就上述那样,但是我们并不清楚谁是A,B,C,所以不能这么定义。
那么我们需要定义什么状态?
当然是表示 同族状态,吃状态,被吃状态。
这是什么意思?由于这是一个并查集,那么我们只要表示儿子和父亲节点的关
系即可,这样我们就可以根据他们的关系来判断谁是假话,谁是真话。
那么我们就定义:
const int same = 0; //我们设置 0 来表示儿子节点 和 父亲结点同族 的关系
const int be_ate = 1; //我们设置 1 来表示儿子结点 被 父亲节点吃 的关系
const int eat = 2; //我们设置 2 来表示儿子结点 吃 父亲节点 的关系
至于我们为什么只设置 0, 1, 2 这三个数字,而不是设置 4 5 6 这些数字,后面我们继续摆
A吃B,B吃C,C吃A 那么这是一个循环,所以我们需要创建一个循环,只有三个状态的循环,那么我们如何进行循环,在我们创建循环队列的时候,
我们是使用的 求余% 来进行解决了,这里我们同样可以使用 求余% 来解决
这就是为什么我们需要选择 0、1、2这三个数来表示状态,只是为了状态的循环
那我们如何来判断儿子同爷爷的关系呢?
我们枚举一下儿子与父亲,父亲与爷爷,儿子与爷爷的关系的关系
儿子与父亲 父亲与爷爷 儿子与爷爷
0 0 0
0 1 1
0 2 2
1 0 1
1 1 2
1 2 0
2 0 2
2 1 0
2 2 1
通过这个枚举我们也可以看出,儿子与爷爷的关系 满足 儿子与爷爷 =(儿子与父亲 + 父亲与爷爷)% 3;
这个关系会运用在哪?当然是压缩路径的时候,如果路径被压缩了,那么我们就要计算儿子与根节点的关系
这个刚刚好,为啥?看代码!!!
(4)上面是find函数,那么接下来就是union函数的,也就是 集合与集合之间的关系
这个函数的意义在于,将所有的集合合并为一个集合,然后我们就可以看到每个元素与根节点的关系,然后我们
就可以判断出 x与y 关系了。
在这里我们设置一下两个集合的根节点为 root_x 和 root_y, 我们设置 x 和 y 分别为这两个集合中的元素
根据并查集的原理,我们晓得压缩路径后的集合高度最多只有两层,所以我们认为
x 相对于 root_x 的关系 为 x->status;
y 相对于 root_y 的关系 为 y->status;
y 相对于 x 的关系 为 d - 1;
那么 我们现在要获得 root_y 相对于 y 的关系
根据相对的原理来说,他们的关系我们先取 负数, 即 -(y->status)
但是 -(y->stast)这是负数,我们并没有这个关系,但是我们可以加 3
由于这是一个循环,加3对于这个循环来说没有太大关系,那么我们就可以得到
root_y 相对于 y 的关系 为 3 - (y->status);
好了,准备工作做完了。我们可以来计算 root_y 相对于 root_x 的关系了
我们设 root_y 相对于 root_x 的关系为 root_y -> status
root_y 相对于 root_x 的关系 = (root_y 相对于 y 的关系 + y 相对于 x 的关系 + x 相对于 root_x 的关系) % 3;
即 root_y -> status = ((3 - y->status) + (d - 1) + x->status) % 3;
然后具体的实现………………………………(不会看代码吗 ^_^)
(5)好了,终于要到最后一部分了,如何判断他们是不是假话
对于这个并查集来说,判断是不是假话,如果不在一个集合,我们只有超出限制的才是假话。
对于同一个集合的,我们就需要判断一下他们同root结点的关系了,
如果他们是同一种类的,他们同root结点的关系一定是一样的
如果他们是相互想吃的,这个我们就简单了,根据相对来说,我们很容易得到
最后我们可以总结出一个判断的公式
d - 1 != (3 - x->status + y->status) % 3 如果满足这个,都是假话。
所以到此,这个题我们都分析完了,接下来就是代码部分
当然笔主代码跟指导的博客主的代码差不多,最后分析出来的公式本人没有实验过,具体自己可以实验一下.
3、代码实现
/**************************************** sovle the problem : POJ 1182 食物链 sovlition : 带权并查集 ****************************************/ #include <cstdio> #include <cstdlib> #include <cstring> #include <iostream> using namespace std; //NUM 是动物的最大容量 const int NUM = 50010; //status 儿子与父亲的状态 const int status_same = 0; // 0 表示 儿子和父亲 同类 const int status_be_ate = 1; // 1 表示 儿子被父亲吃 const int status_eat = 2; // 2 表示 儿子吃父亲 //用一个结构体存储动物的信息 struct Animal { int id; //动物编号 int parent; //父节点 int status; //儿子与父亲的关系 }; Animal animal[NUM]; long ans; //ans 表示假话的数量,需要初始化为 0 //初始化数组的数据 void init_animal(int n) { for(int i =1; i <= n; i++) { animal[i].id = i; animal[i].parent = i; animal[i].status = status_same; } } //寻找父亲结点 int find_parent(Animal* node) { int temp; //判断父亲结点与儿子结点是否为同一个 if(node->parent == node->id) return node->parent; //temp 接收父亲结点 temp = node->parent; //压缩路径,parent and status 都进行压缩 node->parent = find_parent(&animal[node->parent]); //儿子与爷爷的关系 = (儿子与父亲关系 + 父亲与爷爷关系)%3 node->status = (animal[temp].status + node->status) % 3; //上一句一定要在压缩后,因为每次递归都需要确定儿子与爷爷关系 //如果在压缩之前进行,就是确定与原来父节点的关系,然后关系就混乱了 return node->parent; } //联合并集 void union_animal(int x, int y, int a, int b, int d) { //这里为什么不让b为root点,因为 d - 1表示的是 y 相对于 x //d == 2时,d - 1 = 1,表示的是 y 被 x吃的状态 //那么我们可以推出,y 是 x 的子节点 animal[b].parent = a; animal[b].status = ((3 - animal[y].status) + (d - 1) + animal[x].status) % 3; } int main() { < 9bc4 span class="hljs-keyword">int n, m; //n 表示动物数量, m 表示语句数量 int d, x, y; //d 表示状态, x,y表 示某个动物的编号 scanf("%d%d", &n, &m); init_animal(n); //初始化前n个动物的数据 for(int i = 0; i < m; i++) { scanf("%d%d%d", &d, &x, &y); //假话标准第二条 : 如果 x 或者 y的编号大于 n就是假话 if (x > n || y > n) ans++; else { if(d == 2 && x == y) ans++; //d == 2时,表示 x 吃 y,如果 x == y,则表示同一动物, 假话 else { //寻找 x, y的根节点 int a = find_parent(&animal[x]); int b = find_parent(&animal[y]); if(a != b) { //x 和 y不在同一个集合,需要联合 union_animal(x, y, a, b, d); } else { //同一集合中,寻找对错 //因为是同一个 root结点,所以 x,y相对于根节点的状态就可以判断 x 与 y的关系 switch(d) { case 1: // d == 1时, x 与 y 的状态不一致,假话 if(animal[x].status != animal[y].status) ans++; break; case 2: // d == 2时, x 吃 y,即 x 相对于 y的状态为 1 if(((animal[y].status + 3 - animal[x].status) % 3) != 1) ans++; break; } } } } } printf("%d\n",ans); return 0; }
相关文章推荐
- 【POJ1182】食物链,思路+数据+代码,可能是史上关于这道题最详细的解题报告
- POJ1182 并查集 食物链 比较难想到 的问题 并查集 中层次 题
- 【poj 1182】 食物链 题解&代码(C++)
- POJ1182_食物链_并查集判断逻辑问题
- poj 1182:食物链(种类并查集,食物链问题)
- 修道士与野人问题——C++源代码,伪代码,详细分析
- POJ 1182 食物链 (并查集解法)(详细注释)
- 转 :POJ 1182 食物链 ~ 并查集+路径压缩+代码
- poj 1182 食物链 三重并查集问题
- POJ 1182 / Noi 01 食物链 (并查集&代码优化)
- 笔记②:并查集算法解析及其高级运用(POJ1182 食物链代码解析)
- 【poj 1182】 食物链 题解&代码(C++)
- POJ 1182-食物链 并查集
- POJ1182 食物链
- poj1182——食物链
- 并查集经典扩展-pku1182(食物链)-并查集用于等价类问题
- 关于从NAND Flash启动的问题,2440 启动问题 , 拷贝4k程序 ,启动代码分析
- 大家来分析一下,下面代码的严重问题
- 嵌入式Linux内核移植相关代码分析 写的比较详细
- STM32启动代码分析问题