食物链(并查集经典题)

食物链

时间限制:1000 ms | 内存限制:65535 KB  

poj-1182

描述
动物王国中有三类动物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),输出假话的总数。

输入
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
输出
只有一个整数,表示假话的数目。
样例输入
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
样例输出
3

 

这道题很经典,第一次做的带权的并查集,加深了我对并查集的理解与应用,去网上看的代码,有篇博客非常简洁,解题关键代码就只有7.8行的样子吧。没有很详细的注释,就这7,8行,看了我足足6.7小时,

收获挺大,我想把这题关键的部分写下来,以后可以看看,顺便也许能帮到人吧。

这是我参考的一篇博客:很详细,但是说得有点太多了,我都看得累了,代码也有点多了。。。下面的代码才是我说的只有7行关键的简洁代码,我写的类似与那篇博客

http://blog.csdn.net/c0de4fun/article/details/7318642

这道题的代码比较精巧,先说总体思路

刚拿到这题,我想的是能不能将之化为3个并查集ABC,然后每个并查集头结点都记录着这类动物是什么属性,然后发现,根本行不通,因为不好确定到底是哪种属性的动物

思路:这个的思路是将所有说到的动物都放在一个并查集,就是说只要有关系,就连在一起。

1.find_head()找了头结点还压缩了路径到只有两层,这样就很容易判断两者的关系了,压缩路径时,就要适当的改变关系了

2.如果是同一个头结点的话,对头结点的关系要推个式子,下面就说

3.如果不在一个头结点,就说明没有关系嘛。肯定是真话,然后直接连起来就行了。连起来要又需要推导个式子,难住我很久。。。

 

f[N]是存 N 号动物的父节点的编号,r[N]是对父节点的关系 0 同类,1 被父节点吃,2 吃父节点 

find_head()函数递归不但找了头结点,还将搜索路径上的节点都压缩路径,将它直接连接到了头结点,对递归熟悉的不难理解

关键在于确定对 爷爷节点 的那个公式上,穷举一下,然后就能很轻松的看出来的,关系有点像个三角形

 r[f[x]]   r[x]

父亲对爷爷  儿子对父亲  儿子对爷爷 

   0      0       0      所以,儿子对爷爷就可以推出这个式子 (r[x] + r[f[x]] )%3  

   0      1        1   

   0      2       2   

   1      0       1  

   1      1       2  

   1      2       0  

   2      0       2  

   2      1       0     

   2      2       

图:

用这个式子就解决了压缩路径的关系转换

 

然后到了将并查集联合这个函数,也就是 3 的情况,这是最难的部分:

前面说了,用find_head()找头结点,并且直接连上头结点,然后判断两个的头结点是否相等,

  1,相等就判断关系是否矛盾,因为只有两层,枚举一下,容易发现规律,如果 (r[y]-r[x]+3)%3!=d-1  代表假话

或者你足够理解那个find_head()的那个公式 3-r[x] 是 fx 对 x 的关系与 r[y] 相加一下就是 y 对 x 的关系看是否等于d-1。

  2,不同头结点就有点麻烦,像个四边形的关系,所以化成两个三角形。

    3-r[y]是 y 的头结点对 y 的关系,这个好理解吧,

    d-1是 y 对 x 的关系,因为 d 原本是 x 对 y 的关系,减 1 正好反过来。

然后用上我们前面的find里面的那个对父节点的公式, (r[x] +r[f[x]] )%3  ,带进去,就得到 fy 对 x 的关系了,

然后再加上 x 对 fx 的关系就得到了fy 对 fx 的关系, 这不就分成了2个三角形,得到了 r[fy] = 什么。,

不懂,看图:已知标出,要求绿色,先求斜的,再求 r[fy]

 

写完博客,发现更加理解这题了,也更加理解怎么推导公式啥的了,有收获。

 1 #include <stdio.h>
 2 
 3 int const N=50005;
 4 int f[N];
 5 int r[N];
 6 
 7 int find_head(int x)
 8 {
 9     int fx=x;
10     if (x!=f[x])
11     {
12         fx=find_head(f[x]);
13         r[x]=(r[x]+r[f[x]])%3;     //这是穷举可以轻松看出来的公式,压缩路径
14         f[x]=fx;
15     }
16     return f[x];
17 }
18 
19 int Union(int d,int x,int y)
20 {
21 
22     int fx=find_head(x);
23     int fy=find_head(y);
24     if (fx!=fy)
25     {
26         f[fy]=fx;
27         r[fy]=(3-r[y]+d-1+r[x])%3;      //很重要的一个公式,由find里面的公式推导过来
28         return 0;
29     }
30     if ((r[y]-r[x]+3)%3!=d-1) return 1;
31     return 0;
32 }
33 
34 int main()
35 {
36     int n,k;
37     int d,x,y;
38     int ans=0;
39     scanf("%d%d",&n,&k);
40     for (int i=1;i<=n;i++)
41     {
42         f[i]=i;
43         r[i]=0;
44     }
45     while (k--)
46     {
47         scanf("%d%d%d",&d,&x,&y);
48         if (x>n||y>n||(x==y&&d==2)) 
49         {
50             ans++;
51             continue;
52         }
53         if (Union(d,x,y)) ans++;
54     }
55     printf("%d
",ans);
56     return 0;
57 }
View Code

 

原文地址:https://www.cnblogs.com/haoabcd2010/p/5688902.html