这一题有两种思路,先介绍第一种:
题目是中文的,我就不翻译了,也很好理解,就是一个A-B-C-A的一个循环的食物链,给定一些条件,问你哪些条件是错的
这一题,是一道比较经典的查并集的题目,那么这一题的思想是什么呢,对于给定的条件,x和y属于什么集合其实并没有给出,而且x和y之间还有其他的捕食的关系
但是我们现在假设,如果x和y都是属于同一类的,那么如果x属于A,那么y也一定属于A,那么也就是说,x和y属于同一个集合总是同时成立的的,再假设,如果x可以吃y,那么如果x属于A,那么y一定属于B,x属于B,那么y一定属于C或x属于C,那么y一定属于A,也就是说,在捕食关系中,y属于x的下一个集合一定成立的(A-B-C-A)
那么我们就可以用查并集来很好的维护这个关系,我们给x和y赋予A,B,C三个属性,并且用查并集来维护这些属性,如果x和y都属于一个集合,那么我们就要分别合并x和y的三个属性,如果属于不是关系,那么我们就把y的与x的下一个关系的集合进行合并,这三个属性可以用k,k+N,k+2*N的形式表现,这样就可以非常快速的维护关系了(因为对于找根的操作而言,查并集是最快的)
PS:查并集我用的是按大小合并的方式,本来这种方式可以有效的降低搜索的时间,但是因为这一题比较特殊(集合比较多)所以递归时间并没有减少很多,另外大家合并的时候一定要检查是不是同一个集合,不然会出错,并且MLE
另外这题,真是尼玛,有BUG,只能交单次数据(也就是不能while(~scanf())),真是个脑残bug
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 typedef int Position; 6 7 Position Find(Position); 8 void Union_Set(Position, Position); 9 int If_Same_Set(Position, Position); 10 11 static int *Set = NULL; 12 13 int main(void) 14 { 15 int N, case_sum, i; 16 int ans = 0, inform, x, y; 17 scanf("%d%d", &N, &case_sum); 18 Set = new int[3 * (N + 1)]; 19 for (i = 1; i <= 3 * N; i++) Set[i] = -1; 20 for (i = 0; i < case_sum; i++) 21 { 22 scanf("%d%d%d", &inform, &x, &y); 23 if (x > N || y > N)//如果在区域外,直接判断出错 24 { 25 ans++; 26 continue; 27 } 28 else if (inform == 1) 29 { 30 if (If_Same_Set(x, y + N) || If_Same_Set(x, y + 2 * N)) 31 //如果已经是捕食集合中,出错 32 ans++; 33 else//否则,则把xy的三个属性分别合并 34 { 35 Union_Set(x, y); 36 Union_Set(x + N, y + N); 37 Union_Set(x + 2 * N, y + 2 * N); 38 } 39 } 40 else if (inform == 2) 41 { 42 if (If_Same_Set(x, y) || If_Same_Set(x, y + 2 * N)) 43 //如果已经在非捕食集或已统一集合,出错 44 ans++; 45 else 46 { 47 Union_Set(x, y + N); 48 Union_Set(x + N, y + 2 * N); 49 Union_Set(x + 2 * N, y); 50 } 51 } 52 } 53 printf("%d ", ans); 54 delete Set; 55 return 0; 56 } 57 58 Position Find(Position x) 59 { 60 //路径压缩 61 if (Set[x] < 0) 62 return x; 63 else 64 return Set[x] = Find(Set[x]); 65 } 66 67 int If_Same_Set(Position x, Position y) 68 { 69 return Find(x) == Find(y); 70 } 71 72 void Union_Set(Position x, Position y) 73 { 74 Position Rootx, Rooty; 75 Rootx = Find(x); 76 Rooty = Find(y); 77 78 if (Rootx != Rooty)//按大小求并,千万要注意不能是同一个集合的合并,不然会MLE 79 { 80 if (Set[Rootx] < Set[Rooty]) 81 { 82 Set[Rootx] += Set[Rooty]; 83 Set[Rooty] = Rootx; 84 } 85 else 86 { 87 Set[Rooty] += Set[Rootx]; 88 Set[Rootx] = Rooty; 89 } 90 } 91 }
第二种思路,只用到一个查并集和一个偏移量集:思路来源http://poj.org/showmessage?message_id=105601
我们先定义B与A之间的偏移关系:
存在偏移量集delta,如果|delta[B]-delta[A]|%3,则定义
=0 B与A是同类
=1 B是A的食物
=2 B是A的天敌
我们规定,每一次关系的确定,最后B与A都会集中到一个集合(广义集合),其中的关系在偏移量集中找
比如如果规定1吃2,2吃3,4吃2,则说明如果当所有元素的初始化绝对偏移量是0时,则2的绝对偏移量是1,3的绝对偏移量是2(delta[3]-delta[1]=2所以3是1的天敌,符合问题描述),同时4的绝对偏移量是0(绝对偏移量只取012),同样满足关系。但是现在的问题是,我们的集合最后都会搜索到根的,如果我们仅改变某个元素的一次偏移量,可能这个元素就会和根的关系就会发生变化,但是根与其包括的所有元素的关系都是先确定好了,所以我们只用把根的关系都进行偏移就可以,比如我们再定义5吃6,6吃7,8吃6,(偏移量:5-0,6-1,7-2,8-0)同时再定义1吃5,9吃8,如果我们假设5合并到1上,那么5对1的相对偏移量应该是1,而和5这个集合的元素的绝对偏移量应该全部都+1,才能保证相对偏移量不变,也就是可以看成,以5为起点,5这个集合的所有元素全部加上5的绝对偏移量1
比如处理8吃9的时候,因为5的绝对偏移量为1,所以9的偏移量为delta[9]-delta[8]+5的绝对偏移量+1=0-0+1+1=2,刚好说明9是8的食物(此时8的绝对偏移量为0+1),同时也是5的食物,而且还是1的天敌,符合题意
我们最后可以用(delta[x]-delta[y]+d)%3=delta[rootx]的方法来添加绝对偏移量,对于根的绝对偏移量发生变化时,我们可以不必立马对根以下的元素全部进行改变,也改变不了,我们只要查询的时候进行操作再重新记录就可以了
1 #include <iostream> 2 #include <functional> 3 4 using namespace std; 5 6 typedef int Position; 7 8 Position *pos = NULL;//查并集 9 Position *delta = NULL;//偏移量集 10 11 int Find(Position); 12 bool Check(Position, Position, const int); 13 14 int main(void) 15 { 16 int N, case_sum, ans = 0, inform, x, y; 17 scanf("%d%d", &N, &case_sum); 18 pos = new Position[N + 1]; 19 delta = new Position[N + 1]; 20 for (int i = 0; i <= N; i++) 21 pos[i] = i; 22 memset(delta, 0, sizeof(Position)*(N + 1)); 23 24 for (int i = 0; i < case_sum; i++) 25 { 26 scanf("%d%d%d", &inform, &x, &y); 27 if (x > N || y > N) 28 ans++; 29 else if (!Check(x, y, inform - 1)) 30 ans++; 31 } 32 printf("%d ", ans); 33 delete pos; delete delta; 34 return 0; 35 } 36 37 int Find(Position x) 38 { 39 //路径压缩 40 if (pos[x] == x) 41 return x; 42 int tmp = Find(pos[x]); 43 44 delta[x] = (delta[x] + delta[pos[x]]) % 3; 45 pos[x] = tmp; 46 return tmp; 47 } 48 49 bool Check(Position x, Position y, const int inform) 50 { 51 //设delta[x]为x到根的偏移量 52 Position Rootx, Rooty; 53 Rootx = Find(x); 54 Rooty = Find(y); 55 56 if (Rootx == Rooty) 57 { 58 if ((delta[y] - delta[x] + 3) % 3 != inform) 59 return false;//如果在同一个集合,不满足偏移,说明出错了 60 else return true;//满足偏移量,则说明正确 61 } 62 //直接合并 63 pos[Rooty] = Rootx; 64 delta[Rooty] = (delta[x] - delta[y] + inform + 3) % 3; 65 return true; 66 }