您的位置:首页 > 编程语言

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、代码实现

/****************************************
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: