核心解法(并查集):
关键要理清楚这些关系是怎样的:即通过了解题意可知:假设现在有如下三种动物类型:老虎,老虎的食物,老虎的天敌。所输入的x y都只满足这三种类型(输入m次去判断关系),x y是两个独立的未知种类的动物(即不知道是老虎本体、食物还是天敌)。现在需要通过构建且查询个体间的关系,因此我们需要使用并查集,只不过并查集从维护两者间的关系到维护三者间的关系。并且并查集的模板仍然适用,可以直接套,只不过初始化时元素个数由n变为3n。原因如下:以前只需要判断是否为同一组即可所以设置为n,但是此处有本体、食物、天敌这三个组别,不只是判断为是否来自一组的问题。
为了便于存放, 一倍存本身,二倍存食物,三倍存天敌;但我们需要注意:天敌可以吃本身,食物是吃天敌的。
实现代码:
1 #include<bits/stdc++.h> 2 #include <cstring> 3 using namespace std; 4 5 #define MAX_N 500005 6 int n,m,k; 7 int rank[MAX_N]; //树的高度 8 int par[MAX_N]; 9 int ans=0; //记录假话数量 10 int T[MAX_N],X[MAX_N],Y[MAX_N]; 11 void init(){ 12 for(int i=1;i<=3*n;i++){ 13 par[i]=i; 14 rank[i]=0; 15 } 16 } 17 18 int find(int x){ //查询树的根 19 if(par[x]==x){ 20 return x; 21 }else return par[x]=find(par[x]); //在查找的过程中实际上直接将父节点连接到根节点上进行优化 22 } 23 24 void unite(int x,int y){ 25 x=find(x); 26 y=find(y); 27 28 if(x==y) return; 29 30 if(rank[x]<rank[y]){ 31 par[x]=y; 32 }else{ 33 par[y]=x; 34 if(rank[x]==rank[y]) rank[x]++; //两棵树一样高时,合并在x树上后X的高度加1 35 } 36 } 37 38 bool same(int x,int y){ 39 return find(x)==find(y); 40 } 41 42 int main(){ 43 cin>>n>>m; 44 for(int i=1;i<=m;i++){ 45 cin>>T[i]>>X[i]>>Y[i]; 46 } 47 init(); 48 49 for(int i=1;i<=m;i++){ 50 int t=T[i],x=X[i],y=Y[i]; 51 52 if(x<1||x>n||y<1||y>n){ //先判断边界(满足条件2) 53 ans++; 54 continue; //跳过去看下一句话 55 } 56 57 if(t==1){ //表示x y同类时 58 if(same(x,y+n)||same(x,y+2*n)){//如果x是y的猎物或者x是y的天敌(则代表x y这两只动物不同类),显然是谎言( 59 ans++; 60 continue; 61 }else{ //下面都从老虎的视角(本体)来看 62 unite(x,y); //本体和本体是一个类型的,所以要合并 63 unite(x+n,y+n); //食物和食物是一个类型的(即x的食物也是y的食物) 64 unite(x+2*n,y+2*n); //天敌和天敌是一个类型的 (即x的天敌也是y的天敌) 65 } 66 }else if(t==2){ //表示x吃掉y(即y是x的食物关系)时 67 if(x==y){ //若x==y表示是同一个动物,没必要写但可以稍微加快速度 68 ans++; 69 continue; 70 } 71 if(same(x,y)||same(x+2*n,y)){ //表示动物x和动物y是同类型的(都假设是本体来看)或者y是x的天敌(即y吃x),表明这是谎言 72 ans++; 73 continue; 74 }else{ 75 unite(x+n,y); // x的食物与y同类型 76 unite(x+2*n,y+n); //x的天敌与y的食物同类型 77 unite(x,y+2*n); //x与y的天敌同类型 78 } 79 } 80 } 81 cout<<ans; 82 return 0; 83 }