链接:https://ac.nowcoder.com/acm/contest/1031/D
来源:牛客网

题目描述

动物王国中有三类动物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。

输出描述:

只有一个整数,表示假话的数目。
示例1

输入

100 7
1 101 1   
2 1 2     
2 2 3     
2 3 3     
1 1 3     
2 3 1     
1 5 5

输出

3

1.解法一:带权并查集
1)此题设计到多个关系,因此不能用简单的并查集,将出现在K句话中的关系(不是假话的前提下)
全部加入并查集中,用数组P作为x与f[x]之间的具体关系(此处就需要用带权并查集)
2)值得注意的是不能直接用d参加运算,要用d-1,否则结果会与实际不符合
0:同类 1:x吃y 2:x被y吃
3)各个关系的表示跟向量的表示有关系,不妨画个图找找之间的向量关系,
大概有两个图,一个图是在路劲压缩的时候,三个结点之间的关系,另一个图是
在合并的时候x fx y fy之间的关系,都可以画图用向量表示出来

AC代码:
 1 #include <cstdio>
 2 using namespace std;
 3  
 4 int f[50005],p[50005];
 5  
 6 void Init(int n){
 7     for(int i=1;i<=n;i++)
 8         f[i]=i;
 9 }
10 int find(int x){
11     if(x==f[x])
12         return x;
13     int fx=f[x];
14     f[x]=find(fx);
15     p[x]=(p[x]+p[fx])%3;
16     return f[x];
17 }
18 void join(int d,int x,int y){
19     int fa=find(x);
20     int fb=find(y);
21     f[fa]=fb;
22     p[fa]=(3-p[x]+p[y]+d)%3;
23 }
24  
25 int main(void)
26 {
27     int n,k;
28     scanf("%d%d",&n,&k);
29     Init(n);
30  
31     int d,x,y,ans=0;
32     while(k--){
33         scanf("%d%d%d",&d,&x,&y);
34         if(x>n || y>n || (d==2 && x==y))
35             ans++;
36         else{
37             if(find(x)==find(y)){
38                 if(p[x]!=((d-1)+p[y])%3)
39                     ans++;
40             }else{
41                 join(d-1,x,y);
42             }
43         }
44     }
45     printf("%d\n",ans);
46  
47     return 0;
48 }

2.并查集的扩展域

  适用于存在多种关系,将各种关系合并在一个集合中,比如x吃y,那么xeat和y放入一个集合

xenemy和yeat放在一个集合中,x和yenemy放在一个集合中

下面一段话转自博客:

https://blog.csdn.net/qq_41645482/article/details/97391572

前置技能:并查集

   1)拓展域并查集解决了一种多个有相互关系的并查集,放在一起考虑的问题。

一般的并查集应用一般就是判断在不在一个集合,拓展域并查集讲的是多个集合,

之间有相互关系一般为相互排斥关系,判断是否在一个集合等。

    2)首先对与最简单的并查集来说,如果两个是同一类,那么就join(a,b)对吧,

但是对于两个相互排斥类的怎么办呢,这就涉及到拓展与并查集了,首先想法就是建立

两个并查集,但是怎么把两个并查集联系起来呢------拓展个体。

    3)这里的拓展个体是什么意思呢,一个个体我们要拆成多个,比方说两个集合存在

队立关系,那么对于一个个体a,我们假设存在一个个体 a+n  ,a和a+n这两个是处于对

立关系的,所以当我们说 a 和 b对立的时候,意思就是在说,a + n 和 b在同一并查集,

b+n和a在同一并查集,当我们说,a和b是同类的时候,那么也就是说 a和b属于一个并查集,且a+n和b+n属于一个并查集。

    4)这样就建出了多个并查集,解决了多个集合的相互关系。

 

这道算是最经典的拓展域并查集题目了,A吃B,B吃C,C吃A,那么我们就需要三个并查集,也就是原数组扩展3倍。

A和B是同类说明了:

       1. A和B在同一并查集  。  2.A+n和B+n在同一并查集 。  3.A+2*n和B+2*n在同一并查集。

A吃B说明了:

       1.A+n和B是同类。            2.A+2*n和B+n是同类。           3.A和B+2*n是同类。

 

值得注意的是在Init()的时候初始化需要三倍的空间

AC代码:
 1 #include <cstdio>
 2 using namespace std;
 3  
 4 const int N=50001;
 5 int f[N*3];
 6  
 7 void Init(int n){
 8     for(int i=1;i<=3*n;i++)
 9         f[i]=i;
10 }
11 int find(int x){
12     if(x==f[x])
13         return x;
14     return f[x]=find(f[x]);
15 }
16 void join(int x,int y){
17     int fa=find(x);
18     int fb=find(y);
19     if(fa!=fb)
20         f[fa]=fb;
21 }
22  
23 int main(void)
24 {
25     int n,k;
26     scanf("%d%d",&n,&k);
27     Init(n);
28  
29     int d,x,y,ans=0;
30     int xself,xeat,xenemy;
31     int yself,yeat,yenemy;
32     while(k--){
33         scanf("%d%d%d",&d,&x,&y);
34         xself=x,xeat=x+n,xenemy=x+2*n;
35         yself=y,yeat=y+n,yenemy=y+2*n;
36         if(x>n || y>n)
37             ans++;
38         else{
39             if(d==1){
40                 if(find(xeat)==find(yself) || find(yeat)==find(xself)){
41                     ans++;
42                 }else{
43                     join(xself,yself);
44                     join(xeat,yeat);
45                     join(xenemy,yenemy);
46                 }
47             }else{
48                 if(find(xself)==find(yself) || find(xself)==find(yeat)){
49                     ans++;
50                 }else{
51                     join(xeat,yself);
52                     join(xself,yenemy);
53                     join(xenemy,yeat);
54                 }
55             }
56         }
57     }
58     printf("%d\n",ans);
59  
60     return 0;
61 }