在本题目中,有A,B,C三种动物,三种动物之间存在:A捕食B,B捕食C,C捕食A的奇怪捕食关系。本题目中有N([1,5000])只动物,和K([0,100000])个消息,其中存在一些消息为错误的(和之前的消息矛盾即视为错误)。题目让我们求出出错的消息的数量。
首先,需要建立一个并查集,大小为动物总数的三倍。代表每一只动物都有为A动物,B动物,C动物的三种可能。并查集的代码就可以参考书上的代码。我在书上代码的基础上,改了一下变量的名字便于区分。
//并查集数组,其中前三分之一代表A动物,中间的三分之一代表B动物,最后三分之一代表C动物
int disjointSet[MAX_ANIMAL_COUNT*3];
//高度数组,用来路径压缩
int rankArrayForPathCompression[MAX_ANIMAL_COUNT*3];
//初始化并查集
void initDisjointSet() {
for(int i=1; i<(MAX_ANIMAL_COUNT*3); i++) {
//每个节点与自己是同一组
disjointSet[i]=i;
rankArrayForPathCompression[i]=0;
}
//初始化错误消息数量为0
errorMessageCount=0;
}
//查询并查集的树根,顺便进行路径压缩
int findRootNodeAndPathCompression(int nodeId) {
//当前节点就是根节点
if(disjointSet[nodeId]==nodeId) {
return nodeId;
} else {
//向上继续寻找
return disjointSet[nodeId]=findRootNodeAndPathCompression(disjointSet[nodeId]);
}
}
void unite(int nodeIdLeft,int nodeIdRight) {
//寻找各自的根节点
nodeIdLeft=findRootNodeAndPathCompression(nodeIdLeft);
nodeIdRight=findRootNodeAndPathCompression(nodeIdRight);
if(nodeIdLeft==nodeIdRight) {
//本就在同一子树,则不继续合并
return;
}
//不在同一子树,则需要进行合并
if(rankArrayForPathCompression[nodeIdLeft]
接下来,我们来分析,题目中说,输入的动物编号比动物总数还多,即为错误,因此我们写一个方法来判断它。
bool isValidInputAnimalId(int firstAnimalId,int secondAnimalId) {
return (firstAnimalId<=animalCount)&&(secondAnimalId<=animalCount);
}
接下来,我们来分析,当输入“1 X Y”时,如何操作并查集来代表两只动物为同一类动物。我们要考虑三种情况:都为动物A,都为动物B,都为动物C。因此有了接下来的方法。
//合并两只动物为同一组
void mergeAnimalsIntoGroup(int firstAnimal,int secondAnimal) {
//假设二者都为A
unite(firstAnimal,secondAnimal);
//假设二者都为B
unite(firstAnimal+disjointSetRange,secondAnimal+disjointSetRange);
//假设二者都为C
unite(firstAnimal+disjointSetRange*2,secondAnimal+disjointSetRange*2);
}
再然后,我们来考虑“2 X Y”应该如何处理。不难想出,也需要考虑两只动物分别为“A B”,“B C”,“C A”时,因此有了接下来的方法。
//合并两只动物为捕食关系,前者捕食后者
void mergeAnimalEatenRelation(int huntingAnimal,int preyAnimal) {
//假设捕食关系我A与B
unite(huntingAnimal,preyAnimal+disjointSetRange);
//假设捕食关系为B与C
unite(huntingAnimal+disjointSetRange,preyAnimal+disjointSetRange*2);
//假设捕食关系为C与A
unite(huntingAnimal+disjointSetRange*2,preyAnimal);
}
接下来就可以分析两只动物是否为捕食关系,只需要考虑一种动物的编号和(另一只动物的编号+分组区间)即可,所以有了接下来的方法。
//是否左边的动物捕食右边的动物
bool isAnimalLeftEatAnimalRight(int animalLeft,int animalRight) {
bool result=false;
//假如animalLeft为A动物,animalRight为B动物
result=result||same(animalLeft,animalRight+disjointSetRange);
//假如animalLeft为B动物,animalRight为C动物
result=result||same(animalLeft+disjointSetRange,animalRight+disjointSetRange*2);
//假如animalLeft为C动物,animalRight为A动物
result=result||same(animalLeft+disjointSetRange*2,animalRight);
return result;
}
//判断两只动物之间是否为捕食关系
bool isEatenRelationBetweenTwoAnimals(int firstAnimal,int secondAnimal) {
bool result=false;
//判断是否为前者捕食后者
result=result||isAnimalLeftEatAnimalRight(firstAnimal,secondAnimal);
//判断是否为后者捕食前者
result=result||isAnimalLeftEatAnimalRight(secondAnimal,firstAnimal);
return result;
}
然后也就可以分析出如何判断两只动物是否为同一组的方法。
//判断两只动物是否为同一组
bool isTwoAnimalSameKind(int firstAnimal,int secondAnimal) {
bool result=false;
//两只动物都为A动物
result=result||same(firstAnimal,secondAnimal);
//两只动物都为B动物
result=result||same(firstAnimal+disjointSetRange,secondAnimal+disjointSetRange);
//两只动物都为C动物
result=result||same(firstAnimal+disjointSetRange*2,secondAnimal+disjointSetRange*2);
return result;
}
综上,在输入“1 X Y”时,判断是否已经存在捕食关系,如果是,则结果加一,如果不是,则合并二者为一组;在输入“2 X Y”时,判断是否已经为相反(即Y捕食X)捕食关系,是否已经存在两只动物为同一组的捕食关系,如果是则结果计数,否则合并二者的捕食关系。
/*
动物王国中有三类动物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),输出假话的总数。
思路:在本题目中,需要考虑的就是要找出和前面的某些真的话冲突的话.
例如,
*/
#include
using namespace std;
//最大的动物总数
const int MAX_ANIMAL_COUNT=50009;
//最大的消息数量
const int MAX_MESSAGE_COUNT=100009;
//1代表动物之间是同类的关系
const int ANIMALS_OF_THE_SAME_KIND=1;
//0代表动物之间是捕食的关系
const int THE_FORMER_PREYS_ON_THE_LATTER=2;
//表示并查集区间, 分割三个区间,前三分之一代表A动物,中间的三分之一代表B动物,最后三分之一代表C动物
const int disjointSetRange=50000;
//动物数量
int animalCount;
//消息数量
int messageCount;
//错误消息数量
int errorMessageCount;
//并查集数组,其中前三分之一代表A动物,中间的三分之一代表B动物,最后三分之一代表C动物
int disjointSet[MAX_ANIMAL_COUNT*3];
//高度数组,用来路径压缩
int rankArrayForPathCompression[MAX_ANIMAL_COUNT*3];
//初始化并查集
void initDisjointSet() {
for(int i=1; i<(MAX_ANIMAL_COUNT*3); i++) {
//每个节点与自己是同一组
disjointSet[i]=i;
rankArrayForPathCompression[i]=0;
}
//初始化错误消息数量为0
errorMessageCount=0;
}
//查询并查集的树根,顺便进行路径压缩
int findRootNodeAndPathCompression(int nodeId) {
//当前节点就是根节点
if(disjointSet[nodeId]==nodeId) {
return nodeId;
} else {
//向上继续寻找
return disjointSet[nodeId]=findRootNodeAndPathCompression(disjointSet[nodeId]);
}
}
void unite(int nodeIdLeft,int nodeIdRight) {
//寻找各自的根节点
nodeIdLeft=findRootNodeAndPathCompression(nodeIdLeft);
nodeIdRight=findRootNodeAndPathCompression(nodeIdRight);
if(nodeIdLeft==nodeIdRight) {
//本就在同一子树,则不继续合并
return;
}
//不在同一子树,则需要进行合并
if(rankArrayForPathCompression[nodeIdLeft]
每一个小的方法只做一件事情,大的方法作为一个目录,调用小的方法,可以让逻辑更加清晰。
在编写程序过程中,很多时候会因为一个变量没有赋初值或者边界情况考虑不到,导致全盘出错,需要的是静下心调试,分析日志,来寻找原因。