zoukankan      html  css  js  c++  java
  • 【29.42%】【POJ 1182】食物链

    Time Limit: 1000MS Memory Limit: 10000K
    Total Submissions: 64875 Accepted: 19085
    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

    【题解】

    哎。网上有牛人写了很完整的题解。
    我复制一下。

        Part I  - 权值(relation)的确定。 
        我们根据题意,森林中有3种动物。A吃B,B吃C,C吃A。 
        我们还要使用并查集,那么,我们就以动物之间的关系来作为并查集每个节点的 
        权值。 
        注意,我们不知道所给的动物(题目说了,输入只给编号)所属的种类。 
        所以,我们可以用动物之间“相对”的关系来确定一个并查集。 
        0 - 这个节点与它的父节点是同类 
        1 - 这个节点被它的父节点吃 
        2 - 这个节点吃它的父节点。 
    
        注意,这个0,1,2所代表的意义不是随便制定的,我们看题目中的要求。 
        说话的时候,第一个数字(下文中,设为d)指定了后面两种动物的关系: 
        1 - X与Y同类 
        2 - X吃Y 
    
        我们注意到,当 d = 1的时候,( d - 1 ) = 0,也就是我们制定的意义 
                    当 d = 2的时候,( d - 1 ) = 1,代表Y被X吃,也是我们指定的意义。 
        所以,这个0,1,2不是随便选的 
    
    
        Part II - 路径压缩,以及节点间关系确定 
        确定了权值之后,我们要确定有关的操作。 
        我们把所有的动物全初始化。 
        struct Animal 
        { 
            int num; //该节点(node)的编号 
            int parent; //该node的父亲 
            int relation; //该node与父节点的关系,0同类,1被父节点吃,2吃父节点 
        }; Animal ani[50010]; 
            初始化为 
            For i = 0 to N do 
                ani[i].num = i; 
                ani[i].parent = i; 
                ani[i].relation = 0 ; //自己和自己是同类 
            End For 
    
            (1)路径压缩时的节点算法 
            我们设A,B,C动物集合如下:(为了以后便于举例) 
            A = { 1 , 2 , 3 ,4 ,5 } 
            B = { 6 , 7 , 8 ,9 ,10} 
            C = { 11, 12, 13,14,15} 
            假如我们已经有了一个集合,分别有3个元素 
            SET1 = {1,2},我们规定集合中第一个元素为并查集的“代表” 
            假如现在有语句: 
            2 2 6 
            这是一句真话 
            26的父亲 
             ani[6].parent = 2; 
             ani[6].relation = 1; 
            那么,61的关系如何呢? 
             ani[2].parent = 1; 
             ani[2].relation = 0; 
            我们可以发现62的关系是 1. 
            通过穷举我们可以发现 
            ani[now].parent = ani[ani[now].parent].parent; 
            ani[now].relation = ( ani[now].relation + ani[now.parent].relation ) % 3; 
            这个路径压缩算法是正确的 
            关于这个路径压缩算法,还有一点需要注意的地方,我们一会再谈 
            注意,根据当前节点的relation和当前节点父节点的relation推出 
            当前节点与其父节点的父节点的relation这个公式十分重要!! 
            它推不出来下面都理解不了!!自己用穷举法推一下: 
            好吧,为了方便伸手党,我给出穷举过程 
                    i      j 
            爷爷  父亲  儿子  儿子与爷爷 
                   0      0       (i + j)%3 = 0 
                   0      1       (i + j)%3 = 1 
                   0      2       (i + j)%3 = 2 
                   1      0       (i + j)%3 = 1 
                   1      1       (i + j)%3 = 2 
                   1      2       (i + j)%3 = 0 
                   2      0       (i + j)%3 = 2 
                   2      1       (i + j)%3 = 0 
                   2      2       (i + j)%3 = 1 
            嗯,这样可以看到,( 儿子relation + 父亲relation ) % 3 = 儿子对爷爷的relation 
            这就是路径压缩的节点算法 
            (2) 集合间关系的确定 
            在初始化的时候,我们看到,每个集合都是一个元素,就是他本身。 
            这时候,每个集合都是自洽的(集合中每个元素都不违反题目的规定) 
            注意,我们使用并查集的目的就是尽量的把路径压缩,使之高度尽量矮 
            假设我们已经有一个集合 
            set1 = {1,2,7,10} 
            set2 = {11,4,8,13},每个编号所属的物种见上文 
            set3 = {12,5,4,9} 
            现在有一句话 
            2 13 2 
            这是一句真话,X = 13,Y = 2 
            我们要把这两个集合合并成一个集合。 
            直接 
            int a = findParent(ani[X]); 
            int b = findParent(ani[Y]); 
            ani[b].parent = a; 
            就是把Y所在集合的根节点的父亲设置成X所在集合的根节点。 
            但是,但是!!!! 
            Y所在集合的根结点与X所在集合的根节点的关系!!!要怎么确定呢? 
            我们设X,Y集合都是路径压缩过的,高度只有2层 
            我们先给出计算的公式 
            ani[b].relation = ( 3 - ani[Y].relation + ( d - 1 ) + ani[X].relation) % 3; 
            这个公式,是分三部分,这么推出来的 
            第一部分,好理解的一部分: 
            ( d - 1 ) :这是X和Y之间的relation,X是Y的父节点时,Y的relation就是这个 
            3 - ani[Y].relation = 根据Y与根节点的关系,逆推根节点与Y的关系 
            这部分也是穷举法推出来的,我们举例: 
            j 
            子         父相对于子的relation(即假如子是父的父节点,那么父的relation应该是什么,因为父现在是根节点,所以父.relation = 0,我们只能根据父的子节点反推子跟父节点的关系) 
             0             ( 3 - 0 ) % 3 = 0 
             1(父吃子)   ( 3 - 1 ) % 3 = 2 //父吃子 
             2(子吃父)    ( 3 - 2 ) % 3 = 1 //子吃父,一样的哦亲 
            —————————————————————————————————————————————————————— 
            我们的过程是这样的: 
            把ani[Y],先连接到ani[X]上,再把ani[Y]的根节点移动到ani[X]上,最后,把ani[Y]的根节点移动到ani[X]的根节点上,这样算relation的 
            还记得么,如果我们有一个集合,压缩路径的时候父子关系是这么确定的 
            ani[爷爷].relation = ( ani[父亲].relation + ani[儿子].relation ) % 3 
            我们已知道,( d - 1 )就是X与Y的relation了 
            而 (3 - ani[Y].relation)就是 以Y为根节点时,他的父亲的relation 
            那么 
            我们假设把Y接到X上,也就说,现在X是Y的父亲,Y原来的根节点现在是Y的儿子 
              Y的relation   +     ani[Y]根节点相对于ani[Y]的relation 
            ( ( d - 1 )         +    ( 3 - ani[Y].relation) ) % 3 
            就是ani[Y]的父亲节点与ani[X]的relation了! 
    
            那么,不难得到,ani[Y]的根节点与ani[X]根节点的关系是: 
            ( ( d - 1 ) + ( 3 - ani[Y].relation) + ani[X].relation ) % 3 ->应用了同余定理 
            注意,这个当所有集合都是初始化状态的时候也适用哦 
            还是以最开头我们给的三个集合(分别代表三个物种)为例 
            2 1 6 
            带入公式 
            ani[6].relation = ( ( 2 - 1 ) + ( 3 - 0 ) + 0 ) % 3 = 1 
            也就是,61吃 
        Part III - 算法正确性的证明 
            首先,两个自洽的集合,合并以后仍然是自洽的 
            这个不难想吧,数学上有个什么对称性定理跟他很像的。 
            如果理解不了,就这么想!! 
            当set1和set2合并之后,set2的根节点得到了自己关于set1根节点的 
            正确relation值,变成了set1根节点的儿子,那么 
            set2的所有儿子只要用 
            ( ani[X].relation + ani[Y].relation ) % 3就能得到自己正确的relation值了 
            所以说,针对不在同一集合的两个元素的话,除非违背了(2)和(3),否则永远是真的 
            (无论这句话说的是什么,我们都可以根据所给X,Y推出两个子节点之间应有的关系,这个关系一确定,所有儿子的关系都可以确定) 
    
            其实所有的不同集合到最后都会被合并成一个集合的。 
            我们只要在一个集合中找那些假话就可以了。 
            首先,如何判断 
            1 X Y是不是假话。//此时 d = 1 
            if ( X 和 Y 不在同一集合) 
                Union(x,y,xroot,yroot,d) 
            else 
                if x.relation != y.relation  ->假话 
            其次,如何判断 
            2 X Y是不是假话 //此时d = 2 
            if ( X 和 Y 不在同一集合) 
                Union(x,y,xroot,yroot,d) 
            else 
                (ani[y].relation + 3 - ani[x].relation ) % 3 != 1 ->假话 
            这个公式是这么来的: 
            3 - ani[x].relation得到了根节点关于x的relation 
            ani[y] + 3 - ani[x].relation得到了y关于x的relation 
            所以,只要y关于x的relation不是1,就是y不被x吃的话,这句话肯定是假话! 
    
            (2)路径压缩要特别注意的一点(错在这里,要检讨自己) 
                路径压缩的时候,记得要 
                先findParent,再给当前节点的relation赋值。 
                否则有可能因为当前节点的父节点的relation不正确而导致错的稀里哗啦。 
                例子: 
                set1 = {1,2,7,10} 
                set2 = {3,4,8,11} 
                set3 = {12,5,14,9} 
                Union(1,3,1,3,1) 
                Union(3,12,3,12,2) 
                1 5 15的relation 
                如果不先更新parent的relation,算出来应该是 
                ( 3 - 0 + 0 + 1 ) % 3 = 151吃,显然不对 
                这里面,+ 0的那个0是指根节点 12 的relation(未更新,这里的0是指1211的relation) 
                如果更新完了的话,应该是 
                ( 3 - 0 + 2 + 1 ) % 3 = 0 ,51是同一物种,对了 
                这里面的 2 是更新节点12的relation(121的relation) 



    那里把Y接在X的根节点的下方本来应该是这样的。
    这里写图片描述
    但是我们可以换个思路
    变成如下的放法。
    这里写图片描述
    把那三个用红下划线标记的东西累加起来就是relation(a,b)了
    知道relation(a,b)了。
    令fa[b] = a;
    然后再变回
    这里写图片描述
    知道了relation(a,b),我们可以通过lelation(a,b)在路径压缩的时候获取relation(b,y);
    然后是判断是否为假话的问题。
    我们需要判断任意两个点(a,b)的关系;
    因为最后进行完路径压缩每个并查集都只有两层。
    要获取relation(a,b);
    就查看(relation[a]+3-relation[b])%3;
    为0表示相同物种,1表示b吃a,2表示a吃b;
    这样理解(高度只有两层)
    这里写图片描述

    #include <cstdio>
    
    const int MAXN = 59999;
    
    struct node
    {
        int fa, relation;
    };
    
    int n, k,d,x,y,fade = 0;
    node ani[MAXN];
    
    int findfather(int x)
    {
        if (ani[x].fa == x)
            return x;
        int olfa = ani[x].fa;
        ani[x].fa = findfather(ani[x].fa);
        ani[x].relation = (ani[x].relation + ani[olfa].relation) % 3;
        return ani[x].fa;
    }
    
    int main()
    {
        //freopen("F:\rush.txt", "r", stdin);
        scanf("%d%d", &n, &k);
        for (int i = 1; i <= n; i++)
        {
            ani[i].fa = i;
            ani[i].relation = 0;
        }
        for (int i = 1; i <= k; i++)
        {
            scanf("%d%d%d", &d, &x, &y);
            if ((x > n || y > n) || (d == 2 && x==y))
            {
                fade++;
                continue;
            }
            int a = findfather(x);
            int b = findfather(y);
            if (a != b)
            {
                ani[b].fa = a;
                ani[b].relation = (3 - ani[y].relation + d - 1 + ani[x].relation) % 3;
            }
            else
            {
                int temp = (ani[y].relation + 3 - ani[x].relation) % 3;
                if (d == 1)
                {
                    if (temp != 0)
                        fade++;
                }
                else
                    if (d == 2)
                    {
                        if (temp != 1)
                            fade++;
                    }
            }
        }
        printf("%d
    ", fade);
        return 0;
    }
  • 相关阅读:
    HTML5边玩边学(2)
    Get Mac NetBIOS
    NetBios协议详解及网上邻居工作原理
    DXperience Universal 10.1.6
    HTML5边玩边学(1)
    《UML和模式应用》读书笔记(二)
    static instance examples
    自己写的虚拟主机管理系统..和大家分享
    一个不错的net的User Interface Components
    Infragistics系列控件
  • 原文地址:https://www.cnblogs.com/AWCXV/p/7632184.html
Copyright © 2011-2022 走看看