链接: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和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
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 }
浙公网安备 33010602011771号