首页 > 代码库 > UnionFind1182

UnionFind1182

Description

 

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。AB, BCCA 

现有N个动物,以1N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。 

有人用两种说法对这N个动物所构成的食物链关系进行描述: 

第一种说法是"1 X Y",表示XY是同类。 

第二种说法是"2 X Y",表示XY 

此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。 

1) 当前的话与前面的某些真的话冲突,就是假话; 

2) 当前的话中XYN大,就是假话; 

3) 当前的话表示XX,就是假话。 

你的任务是根据给定的N1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。 

 

Input

 

第一行是两个整数NK,以一个空格分隔。 

以下K行每行是三个正整数 DXY,两数之间用一个空格隔开,其中D表示说法的种类。 

D=1,则表示XY是同类。 

D=2,则表示XY

 

//与父节点的关系,0:同类,1:吃根节点,2:被根节点吃  

并查集的优势在于可以将所有类型的数据放在一个集合,通过数组r来表示各个元素与根节点的关系,同时每个元素访问根节点非常快(路径压缩),进而可以判断新来的两个元素关系与旧关系是否存在矛盾。

 

 

#include <cstdio>

#include <cstring>

#include <iostream>

using namespace std;

 

const int NN=50005;

 

int n;

int p[NN]; //根节点

int r[NN]; //与父节点的关系,0:同类,1:吃根节点,2:被根节点吃

 

inline void get(int &x)

{

    char c=getchar();

    while (c<‘0‘ || c>‘9‘) c=getchar();

    x=c-‘0‘;

    c=getchar();

    while (c>=‘0‘ && c<=‘9‘) x=x*10+c-‘0‘,c=getchar();

}

 

int find(int x)

{

    if (p[x]!=x)

    {

        int t=p[x];

        p[x]=find(p[x]);

        r[x]=(r[x]+r[t])%3; //x与新父节点(根节点)的关系。r[f]是x的父节点,当进入//递归以后,变成find[f],从而得到r[f]的值(r[f]=r[f]+r[s]s是根节点,r[f]是全局变量),返回//上一层递归的时候,旧r[x]仍然表示向量(f,x)(x与其旧根节点f相连的向量),与新向量(s,f)相加得到s,x向量,即r[x]-r(s)=r[x]-0=r[x]

///////////////////////////////////////        ani[now].parent = ani[ani[now].parent].parent;

        ani[now].relation = ( ani[now].relation + ani[now.parent].relation ) % 3;

        嗯,这样可以看到,儿子relation + 父亲relation ) % 3 = 儿子对爷爷的relation

        这就是路径压缩的节点算法,反应在向量图里:

 

按照右图的方向r(s,x)=r(s,f)+r(f,x),t是x的旧父节点,s是t的新父节点,s是x新父节点

///////////////////////////////////////

 

 

    }

    return p[x];

}

 

int Union(int d,int x,int y)

{

    int fx=find(x);//接入之前首先改变节点书上各个节点关系。即UNION操作之后其实并不是所有节点的R[X]都是正确的

    int fy=find(y);

    p[fx]=fy;//当把FX接到FY的后面时候,需要改变的是X与X的根节点的关系,即R[X],在以后的FIND过程中再来修改X下面各个节点的R值

    r[fx]=(r[y]-r[x]+2+d)%3; //

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

R(fx)-r(fy)=r(fx)-0=r(fx)=r(fy,fx)=r(fy,y)+r(y,x)+r(x,fx) 

其中r(x,fx)=r(fx)-r(x)=0-r(x)=-r(x)(此刻的r(x)针对的根节点是r1,而不是r2,这里看到迟点修改下面节点的意义了)

r(fy,y)=r(y)-r(fy),即r[fy]。

R(y,x)=d-1(加上2,防止出现负值),d=1时,X,Y是同类,r(x,y)=0,表示两者一样的,当d=2,x吃y,r(y,x)=r(x)-r(y)=1,在后面的FIND过程中,X和Y的parent域都需要指向r2。假设y与r2同类,则x吃y也吃r2,因此r[y]=0,r[x]=1(d=1表示吃父节点)正好匹配。

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

}

 

int main()

{

    int d,x,y,k,cnt=0;

    scanf("%d%d",&n,&k);

    for (int i=1; i<=n; i++)

    {

        p[i]=i;

        r[i]=0;

    }

    while (k--)

    {

        get(d); get(x); get(y);

        if (x>n || y>n) cnt++;

        else if (d==2 && x==y) cnt++;

        else if (find(x)!=find(y)) Union(d,x,y);

//对于一个新来的d,x,y,如果xy不再一个并查集里,则无法判断该句话正确与否,因为说明是新来的一对(可能是其中的一个是第一次出现,或者两个都是,不能用find(x)==x || find(y)==y,判断,因为有可能find(x)=x,find(y)=x两个都在),在已经存在的集合里找不到这样的一对的,而要想找到矛盾必须与之前的比较菜可以,所以只好加进来,

        else if ((r[y]+d+2)%3!=r[x]) cnt++;

//这句话一定是在find(x)find(y)之后的,因为find过后r[x]才是x与其根节点的关系,这里的意义是:如果xy在同一个集合里,那么分两种情况:d=1xy属于一类,因此r[x]=r[y],因此 (r[y]+d+2)%3=r[x] ,d=2xyr[x]-r[y]=1,也满足(r[y]+d+2)%3=r[x],即r[x]-r[y]=(d+2)%3

    }

    printf("%d\n",cnt);

    return 0;

}

 

 

UnionFind1182