• POJ 1182 食物链(经典并查集) (多组输入有时乱加也会错!)


     
    多组输入有时乱加也会错!
    这次用多组输入竟然,不用竟然对了,所以以后做题目,若是答案错误,先看加上或者删掉多组输入,看对不对
    食物链
    Time Limit: 1000MS   Memory Limit: 10000K
    Total Submissions: 68854   Accepted: 20363

    Description

    动物王国中有三类动物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

    Source

    这道题真的是一道经典的并查集呀
    以下来自网上一篇博客的解释,写的蛮好的,在这里直接套用了,因为不知道博主是谁,所以抱歉无法写出他的名字
    我是在下面这个博客上面看到的
    http://blog.csdn.net/gemire/article/details/20566907

    题意:一共就三种动物,如果A吃B,B吃C==》C吃A;

                                        A吃B,A吃C==》B、C为同类

                                  A被B吃,A被C吃==》B、C为同类

    用并查集来做:

    两种动物之间的关系通过于根节点的相对关系得出,所以关键是路径压缩与合并两个集合时的动物与根节点相对关系的变化,其实也可认为是一个问题,因为路径压缩中的变化其实是合并集合产生的子问题。

    用delta【i】来表示i和i的父节点的关系,rank[i]=0/1/2分别表示 i 与父亲是同类、被父亲吃、吃父亲。

    先讲合并操作:

    设tx为x的父亲,ty是y的父亲,所以delta[x]表示x和tx的关系,delta[y]表示y与ty的关系,现在,合并操作要将ty的父亲置为tx,所以delta[ty]的值就要发生相应的改变,即产生了新的ty与tx的关系;

    那么,如何求这个关系呢?有两种方法:第一种,可以通过实际数据推出来,

    tx       ty

    |          |

    x   ~    y

    知道了tx与x的关系,x与y的关系,y与ty的关系,tx与ty的关系自然就推出来了

    type表示x与y的关系0为同类,1为x吃y

    type 0 0 0 0 1 1 1 1 1                    
    delta[x] 0 0 0 1 0 1 2 2 1                    
    delta[y] 0 1

    2

    2 2 2 1 2 1                    
    delta[ty] 0 2 1 2 2 0 2 1 1                    

    仔细再想想,tx-x 、x-y、y-ty,是不是很像向量形式,于是便有了一般化的结论:来自北大discuss

    tx       ty

    |          |

    x   ~    y

    对于集合里的任意两个元素x,y而言,它们之间必定存在着某种联系,

    因为并查集中的元素均是有联系的,否则也不会被合并到当前集合中。那么我们

    就把这2个元素之间的关系量转化为一个偏移量,以食物链的关系而言,不妨假设

    x->y 偏移量0时 x和y同类

    x->y 偏移量1时 x吃y

    x->y 偏移量2时 x被y吃,也就是y吃x

    有了这些基础,我们就可以在并查集中完成任意两个元素之间的关系转换了。

    不妨继续假设,x的当前集合根节点tx,y的当前集合根节点ty,x->y的偏移值为d-1(题中给出的询问已知条件)

    (1)如果tx和ty不相同,那么我们把ty合并到tx上,并且更新deltx[ty]值(注意:deltx[i]表示i的当前集合根节点到i的偏移量!!!!

        此时 tx->ty = tx->x + x->y + y->ty,可能这一步就是所谓向量思维模式吧

        上式进一步转化为:tx->ty = (deltx[x]+d-1+3-deltx[y])%3 = deltx[ty],(模3是保证偏移量取值始终在[0,2]间)

    (2)如果tx和ty相同,那么我们就验证x->y之间的偏移量是否与题中给出的d-1一致

        此时 x->y = x->tx + tx->y = x->tx + ty->y,

        上式进一步转化为:x->y = (3-deltx[x]+deltx[y])%3,
        若一致则为真,否则为假。

    牛的想法啊!

    凡人就模仿着学习啦~哈哈

    接下来把这个想法再运用到路径压缩中:

    ffx   

    |     

    fx     

    |         /

    |     /

    x     

    路径压缩过程中会将fx的父亲变为x的父亲,所以要改变相对关系,即偏移量。

    ffx->fx+fx->x=ffx->x;

    转换成:delta[x]=(delta[fx]+delta[x])%3;

    下面是个人理解

    这道题目的大概意思是先用t[]来存下两个动物之间的关系,0代表x和y是同类,1代表x吃y,2代表x被y吃

    题目输入的是1,2,所以加入join时要type-1,大的else是用来跟新t[ty]的

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    int pre[50010],t[50010],n;
    int find(int x)
    {
        if(x==pre[x])
            return x;
        int tx=find(pre[x]);
        t[x]=(t[x]+t[pre[x]])%3;//关键,跟新路径
        return pre[x]=tx;
    }
    int join(int x,int y,int type)
    {
        if(x>n||y>n)
            return 1;
        if(type==1&&x==y)
            return 1;
        int tx=find(x);
        int ty=find(y);
        if(tx==ty)
        {
            if((t[y]-t[x]+3)%3!=type)
                return 1;
            else return 0;
        }
        else
        {
            pre[ty]=tx;
            t[ty]=(t[x]-t[y]+type+3)%3;//关键,加3为了防止负数的出现!!!
            return 0;
        }
    }
    int main()
    {
        int type;
        int num,x,y,k;
        while(~scanf("%d%d",&n,&k))
        {
            num = 0;
            for(int i=1;i<=n;i++)
            {
                pre[i] = i;
                t[i] = 0;
            }
            while(k--)
            {
                scanf("%d%d%d",&type,&x,&y);
                if(join(x,y,type-1))
                    num++;
            }
            printf("%d
    ",num);
        }
        return 0;
    }
    彼时当年少,莫负好时光。
  • 相关阅读:
    Android Handler研究(1)
    知识树杂谈Java面试(4)
    BAT级别分类
    知识树杂谈Android面试(3)
    知识树杂谈(2)
    mac iterm2安装、sshpass密码记住
    一万小时定律
    第17章(下)--集合
    第17章(中)--集合
    第17章(上)--集合
  • 原文地址:https://www.cnblogs.com/l609929321/p/6561850.html
Copyright © 2020-2023  润新知