美文网首页
【数字_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拆点 并查集)

    编辑:数字_ID 时间:2018年5月22日 1. 写在题前 一道非常经典的且有意思的并查集题目,所以打算放上原题...

  • 并查集专题整理

    kuangbin专题 模板 关于并查集的一点心得 大家都说带权并查集的起点是食物链( POJ - 1182 ),但...

  • 2016-12-27 带权并查集与子树自检

    带权的必要性 并查集的性能劣化最主要的是来源于子树过于庞大合并和判断需要时间过长,那么就需要通过带权来使得并查集在...

  • 并查集专题

    主要解决某些判断并找出这些逻辑相悖的条件,带权并查集可以解决区间和逻辑出错的判断问题,并查集厉害的地方就是路径压缩...

  • Beauty of Trees(带权并查集)

    题目 https://www.nowcoder.com/acm/contest/119/A 题目大意 每次查询告诉...

  • 食物链//并查集

    题目描述 动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。A 吃 B,B 吃 C,C 吃 A...

  • HDU3038(How Many Answers Are Wro

    链接:https://vjudge.net/problem/HDU-3038思路:带权并查集,首先我们要考虑在什么...

  • markdown学习

    #并查集 ##并查集的定义 ##并查集的操作

  • 算法模板(四)并查集

    并查集 路径压缩并查集

  • 并查集入门使用

    本文参考自《算法笔记》并查集篇 并查集的定义 什么是并查集?并查集可以理解为是一种维护数据集合的结构。名字中并查集...

网友评论

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

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