对于POJ 1182 食物链 问题的详细分析加代码

来源:互联网 发布:淘宝店铺内衣名字大全 编辑:程序博客网 时间:2024/05/22 11:47

题目: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(){    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;}