分类并查集+并查集的复习

动物王国中有三类动物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),输出假话的总数。 

Input

第一行是两个整数N和K,以一个空格分隔。 
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。 
若D=1,则表示X和Y是同类。 
若D=2,则表示X吃Y。

Output

只有一个整数,表示假话的数目。

Sample Input

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

Sample Output

3

我们先回顾一下并查集的基本操作

初始化

for(int i=1;i<=n;i++) father[i]=i;//令结点等于i,father[i]也可以等于-1

查找

递推代码

int findfa(int x){
    while(x!=father[x]){//不是根节点,继续循环
        x=father[x];//获得自己的父节点
    }
}

递归代码

int findfa(int x){
    if(father[i]==x) return x;
    else return findfa(father[x]);
}

合并

void Union(int a,int b){
   int faA=findfa(a),int faB=findfa(b);
   if(faA!=faB) father[faB]=faA;//一定要父节点相连,不然会丢失结点
}

路径压缩

递推的压缩

int findfa(int x){
    int a=x;
    while(x!=father[x]){
        x=father[x];
    }
    while(a!=father[a]){
        int z=a;
        a=father[a];
        father[z]=x;
   }
  return x;
}

递归的压缩

int findfa(int v){
   if(v==father[v]) return v;
   else{
    int F=findfa(father[v]);
    father[v]=F;
    return F;
   }
}

以上是最基本的并查集操作,那我们再来分析一下这道题。

我们首先要知道,题目是让我们求关系是否正确,而不是求两两之间的关系,我们就可以根据有无关系来进行分类。有关系的就放到一个并查集中去,没有的就不用管,再在这个并查集中去验证关系是否正确。

为了关系的简化,我们将并查集中的所有关系进行路径压缩,每个点只与根节点判断关系,这样操作是最省时间的。

   为了表达关系,我们开一个r【】数组,用来存p[x]->x的关系,

                                            我们设 r[x]=0, 就是只p[x]与x同类

                                               r[x]=1,  p[x]吃掉x

                                               r[x]=2, x吃掉p[x]

现在最难的就是关系的更新

我们假设 p[x]吃x x吃y 那么y和p[x]的关系是什么呢?          很显然,y吃p[x], 食物链的关系 p[x]->x->y->p[x]。

我们现在分开写 p[x]->y=p[x]->x+x->y; 是不是像向量形式(其实我是看的大佬的博文)我们继续验证 r[y]=r[x]+1=2 确实是y吃p[x]

(因为此时x与y的关系已知,y的父节点就是x的父节点p[x]),知道这个后,我们就可进行实现了。

#include <queue>
#include <cstdio>
#include <set>
#include <string>
#include <stack>
#include <cmath>
#include <climits>
#include <map>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
#include <stdio.h>
#include <ctype.h>
#define  LL long long
#define  ULL unsigned long long
#define mod 1000000007
#define INF 0x7ffffff
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N=50005;
int p[N],r[N];
int findfa(int x)
{
   if(x!=p[x]){
        int fa=findfa(p[x]);
        r[x]=(r[x]+r[p[x]])%3;//我们可以理解为一个链 p[x]->x->x1->x2  p[x]->x2=p[x]->x+x->x1+x1->x2 每一步都是自己加上父节点的r关系
 
        p[x]=fa;
    }
    return p[x];

}
int Union(int d,int x,int y)
{
    int fax=findfa(x),fay=findfa(y);
    if(fax==fay){
        if(d-1!=((-r[x]+3+r[y])%3)) return 1;//x与y同父亲(p[x]=p[y])  x->y=x->p[x]+p[x]->y.注意符号,p[x]到x的关系为r[x],x到p[x]的关系为-r[x]
        else return 0;
    }
    p[fay]=fax;
    r[fay]=(r[x]+d-1+3-r[y])%3;//这里找rootx->rooy=rootx->x+x->y+y->rooty 注意符号
    return 0;
}
int main()
{
    int n,q,d;
    int cot=0;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++) p[i]=i,r[i]=0;//并查集的初始化
    int x,y;
    while(q--){
        scanf("%d%d%d",&d,&x,&y);

        if(x>n||y>n||(x==y&&d==2)){cot++;continue;}
        if(Union(d,x,y)) cot++;
    }
    printf("%d\n",cot);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40620465/article/details/83342386