美文网首页
【数字_ID】POJ-1182-食物链(带权or拆点 并查集)

【数字_ID】POJ-1182-食物链(带权or拆点 并查集)

作者: 数字_ID | 来源:发表于2018-05-22 18:53 被阅读0次
    • 编辑:数字_ID
    • 时间:2018年5月22日

    1. 写在题前

    • 一道非常经典的且有意思的并查集题目,所以打算放上原题,慢慢讲
    • 2018西安邀请赛的热身赛有一道类似的,是说,有n个人,逐渐给出k句话,每句话给出i,j,表示ij中有一个是叛徒,问你矛不矛盾,如果矛盾,说出最早出现矛盾的是第几句话,如果不矛盾,给出叛徒的个数可能的最大值,和这道题基本一毛一样

    2. 题目

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

    3. 关于并查集

    • 非常优雅
    • 记录每个点的最上面的父亲节点,可以理解为记录每个节点的“祖先”
    • 网上教程和例题很多,我就给出一个短小的常规操作好了
    void init()
    {
        for(int i = 0;i<n;i++)
        {
            fa[i] = i;
        }
    }
    int find(int x)
    {
        return (fa[x] == x)?x:fa[x] = find(fa[x]);  
    }
    
    void merge(int x,int y)
    {
        int fx = find(x);
        int fy = find(y);
        if(fx!=fy)
        {
            fa[fx] = fy;
        }
    }
    

    4. 关于这道题

    • 有两种解法,由于自己做的题少,也是通过这道题第一次接触带权并查集和拆点并查集这两种玩法

    1. 带权

    • 对于每个节点,给他一个权值,可以用val[]数组保存,用来表示他和父亲的关系,其中,0表示和父亲同类,1表示被父亲吃,2表示吃父亲,为什么这么搞呢?我们可以分析一下这样做,他的两种关系转移特征
      1. 假设A是B的父亲,B是C的父亲,已知A吃B,B吃C,那么A和C的关系是什么呢?
        B是A的儿子同时是C的父亲.png
        A吃B,B吃C,那么val[B] == 1,val[C] == 1,在find过程中会变成B,C同父,C的父亲变了,自然权值也就变了,根据题意,此时是C吃A,val[C] ==2,其实稍微想一下会发现,这个过程中,关系转移方程式val[C] = (val[B]+val[C])%3
      2. 假设BC有相同的父亲A,已知他们和A的关系,求BC的关系 B和C相同父亲.png
        其实此时,BC关系的方程式为(val[B]-val[B]+3)%3
    • 知道关系转移式之后,就可以轻松地切这道题了

    2. 拆点

    • 这个方法理解起来需要一点时间,我觉得网上说的平行宇宙比喻很有意思
    • 既然有三种关系,我们就假设有三个平行宇宙
    • 如果x吃y,就让宇宙1的x和宇宙2的y归为一类,宇宙2的x和宇宙3的y归为一类,宇宙3的x和宇宙1的y归为一类,相反的,如果宇宙3的x和宇宙1的y是一类,那么就说明宇宙1中x吃y
    • 如果x和y同类,就让每个宇宙中的x和y都归为一类
    • 利用这个逻辑去判断是否矛盾。比如,如果题目说x和y是同类,但是你发现宇宙2的x和宇宙3的y是一类,也就是你发现在你的记录中x吃y,那么就产生矛盾了,ans++,其他同理

    5. 代码

    带权代码

    #include<iostream>
    #include<string.h>
    #include<stdio.h>
    
    using namespace std;
    
    const int maxn = 50020;
    
    int fa[maxn],val[maxn];//val数组表示每个节点和其father的关系,0表示同类,1表示被吃,2表示吃
    int n,k;
    void init()
    {
        for(int i = 0;i<n;i++)
        {
            fa[i] = i;
            val[i] = 0;
        }
    }
    int find(int x)
    {
        if(fa[x] == x)return fa[x];
        else
        {
            int temp = fa[x];
            fa[x] = find(fa[x]);
            val[x] = (val[x]+val[temp])%3;
            return fa[x];
        }
    }
    void merge(int x,int y,int t)
    {
        int fx = find(x);
        int fy = find(y);
        if(fx!=fy)
        {
            fa[fx] = fy;
            val[fx] = (val[y]-val[x]+t+3)%3;        
        }   
    }
    
    bool isOK(int x,int y,int t)
    {
        if(x>n||y>n)return false;
        if(x == y&&t!=0)return false;
        if(find(x) == find(y))
        {
            return (val[x]-val[y]+3)%3 == t;
        }
        else return true;
    }
    
    int main()
    {
        scanf("%d%d",&n,&k);
        int ans = 0;
        int x,y,t;
        init();
        while(k--)
        {
            scanf("%d%d%d",&t,&x,&y);
            if(isOK(x,y,t-1))
            {
                merge(x,y,t-1);
            }
            else ans++;
        }
        cout<<ans<<endl;
    }
    

    拆点代码

    #include<iostream>
    
    using namespace std;
    
    const int maxn = 150020;
    
    int fa[maxn];
    
    int n,k;
    void init()
    {
        for(int i = 0;i<3*n;i++)
        {
            fa[i] = i;
        }
    }
    int find(int x)
    {
        return (fa[x] == x)?x:fa[x] = find(fa[x]);  
    }
    
    void merge(int x,int y)
    {
        int fx = find(x);
        int fy = find(y);
        if(fx!=fy)
        {
            fa[fx] = fy;
        }
    }
    bool isOK(int x,int y,int t)
    {
        if(x == y&&t!=1)return false;
        if(x>n||y>n)return false;
        
        if(t == 1)
        {
            if(find(x) == find(y+n)||find(x) == find(y+2*n))return false;
            else return true;
        }
        else
        {
            if(find(x) == find(y)||find(x) == find(y+2*n))return false;
            else return true;
        }
    }
    int main()
    {
        scanf("%d%d",&n,&k);
        int ans = 0;
        init();
        while(k--)
        {
            int x,y,t;
            scanf("%d%d%d",&t,&x,&y);
            if(isOK(x,y,t))
            {
                if(t == 1)
                {
                    merge(x,y);
                    merge(x+n,y+n);
                    merge(x+2*n,y+2*n);
                }
                else
                {
                    merge(x,y+n);
                    merge(x+n,y+2*n);
                    merge(x+2*n,y);
                }
            }
            else ans++; 
        }
        cout<<ans<<endl;    
    } 
    

    相关文章

      网友评论

          本文标题:【数字_ID】POJ-1182-食物链(带权or拆点 并查集)

          本文链接:https://www.haomeiwen.com/subject/okjkjftx.html