题目大意
动物王国中有三类动物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),输出假话的总数。
题目分析
由于给出X和Y的相对关系,并不能确定X和Y分别是哪种动物,因此没法将动物i属于A、B、C中的哪一类作为一个集合。但是,题目是典型的集合操作,判断两个元素是否符合某种关系,因此仍然考虑使用并查集来解决。
使用并查集这种结构时候,需要维护相关的数据信息。考虑每两个动物只要能够确定相互关系,则将他们加入一个集合,对于集合中的每个动物,我们维持
该动物和该集合的根节点动物的相对关系。在每次获得两个动物X和Y的相对关系的时候,对于之前已经知道的那些和X或Y相关的动物的关系,只需要知道这些动
物和该共同集合的根节点动物的相对关系,则可以确定他们之间的相对关系。
在每次得到X和Y之间的关系rel时,先获得他们的根节点,如果根节点p相同,则可以通过X和p的关系,Y和p的关系,确定X和Y在之前就可以确
定的关系rel_1。将rel_1和本次输入的X和Y之间的关系rel进行对比,如果不同,则说明出错;如果根节点不同,分别为px,
py,则将px和py合并,然后根据X和px的关系,X和Y的关系,y和py的关系,得到px和py的关系。
关于带权并查集
带权并查集和普通并查集最大的区别在于带权并查集合并的是可以推算关系的点的集合(可以通过集合中的一个已知值推算这个集合中其他元素的值)。而一般并查集合并的意图在于这个元素属于这个集合。带权并查集相当于把“属于”的定义拓展了一下,拓展为有关系的集合。
实现(c++)
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #define MAX_ANIMAL_NUM 50010 int gPar[MAX_ANIMAL_NUM]; //集合的根节点 int gRel[MAX_ANIMAL_NUM]; //节点和所属集合的根节点之间的关系 //gRel[i] == 0, i 和它的最老祖先是同类动物 //gRel[i] == 1, i 吃它的最老祖先 //gRel[i] == 2, i 的最老祖先吃它 void Init(int n){ for (int i = 1; i <= n; i++){ gPar[i] = i; gRel[i] = 0; } } //获得集合的根节点,同时维护信息,主要是更新 节点到跟节点的关系数组 gRel[x] int GetPar(int c){ if (c != gPar[c]){ int p = gPar[c]; //c还没被进行路径压缩前,节点c所属集合的根节点 p gPar[c] = GetPar(gPar[c]); //进行路径压缩,路径压缩之后,递归函数返回时,p已经设置了根节点,同时也设置了p和总集合的根节点的关系 gRel[c] = (gRel[c] + gRel[p]) % 3; //根据c和p的关系,以及p和总集合的根节点的关系,设置c和总集合的根节点的关系 } return gPar[c]; } int main(){ int N, K; scanf("%d %d", &N, &K); int rel, x, y; int error_count = 0; Init(N); for (int i = 0; i < K; i++){ scanf("%d %d %d", &rel, &x, &y); if (x > N || y > N){ error_count++; continue; } rel--; int p1 = GetPar(x), p2 = GetPar(y); if (p1 == p2){ if ((gRel[x] + 3 - gRel[y]) % 3 != rel){ //关系不一致 error_count++; } } else{ //对集合进行合并,同时需要注意,根据 x-->p1, x->y, y->p2的关系,得到 p1->p2的关系 gPar[p1] = p2; gRel[p1] = (3 - gRel[x] + rel + gRel[y]) % 3; } } printf("%d\n", error_count); return 0; }