洛谷-P2024 食物链 并查集应用

题目描述
动物王国中有三类动物 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 句话有的是真
的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
• 当前的话与前面的某些真的话冲突,就是假话
• 当前的话中 X 或 Y 比 N 大,就是假话
• 当前的话表示 X 吃 X,就是假话
你的任务是根据给定的 N 和 K 句话,输出假话的总数。
输入输出格式
输入格式:
从 eat.in 中输入数据
第一行两个整数,N,K,表示有 N 个动物,K 句话。
第二行开始每行一句话(按照题目要求,见样例)
输出格式:
输出到 eat.out 中
一行,一个整数,表示假话的总数。

本题需要知道并查集结构 要用到其中的merge和find操作

首先理解题意:一开始我想的是如果出现某句话与之前的一句话矛盾,那到底哪句有问题?其实题目的意思是如果当前这句话判断不出它有问题,那么就是真话,先说的没问题的就是真话。

分析:
一开始考虑先假设第一个动物1是物种A,然后如果1吃2,就让2放到物种B中,如果又有2吃3,就让3到物种C中,现在问题是,如果又来了一个4吃5 , 4跟5要放到哪里去?这就是这个问题最不好处理的一个地方。

这个问题的关键是无法确定每一句真话所说的两个动物到底是ABC中的哪一种,ABC构成一个环,也就是说其实最终的可能物种分布有3种可能,而现在只能确定它们之间的关系,因此这里要使用一个并查集的应用技巧,即开更大的并查集空间,就是说既然动物之间都只能确定一个相对关系而无法它们到底是哪一种,那么干脆就让ABC三种物种中都设定好每一种动物,例如:
假设现在有4种动物 : 1 2 3 4
那么就存 A1 A2 A3 A4 B1 B2 B3 B4 C1 C2 C3 C4
这样做有什么好处?
现在假设要表示1和2是同一物种,只需要将A1 A2做并查集中的merge操作 B1 B2也做merge C1 C2也做merge
现在假设要表示1吃2,只需要将A1 B2 merge B1 C2 merge C1 A2 merge
也就说这样做就回避了确定动物具体是哪种物种的问题,把三种情况都设置好了
如果说A中的某个动物和B中的某个动物被并查集归为一类,就说明这两个动物构成了吃与被吃的关系,如果说A中的某个动物和B中的某个动物被并查集归为一类,就说明这两个动物构成同类关系。
那么现在看如何判断是否为假话,一种是题目给的容易判断的情况,另一种是与真话矛盾的情况:
假设某句话说两个物种a,b是同类,只需要判断在之前的真话中,是否能推出这两个物种构成了吃与被吃的关系,即判断Aa与Bb是否为一类(a吃b) Aa与Cb是否为一类(b吃a),如果满足,就说明这句话是假话
假设某句话说两个物种a,b a吃b,只需要判断在之前的真话中,是否能推出这两个物种构成了同类关系或者 b吃a的关系,即判断Aa与Ab是否为一类 Cb与Aa是否为一类,如果满足,就说明这句话是否为假话
这样就很好的回避了一开始那种想法遇到的问题,不知道放哪里就都放嘛,反正不影响我们的相对关系。

#include <iostream>
using namespace  std;

int father[300005];
int depth[300005];

int find(int x )
{
    return x == father[x] ? x : father[x] = find(father[x]); //路径压缩
}

void merge(int a , int b) //a吃b
{
    int fa = find(a);
    int fb = find(b);
    if ( fa != fb )
    {
        if (depth[fa] == depth[fb])
        {
            father[fa] = fb;
            depth[fb] ++;
        }
        else if (depth[fa] < depth[fb])
        {
            father[fa] = fb;
        }
        else {
            father[fb] = fa;
        }
    }
}


int main()
{
    int n, m;
    int result = 0;
    cin >> n >> m;
    for (int i = 1 ; i <= 3 * n ; i ++ )
    {
        father[i] = i;
    }
    for (int i = 0 ; i < m ; i ++ )
    {
        int relation, a, b;
        cin >> relation >> a >> b;
        if (a > n || b > n || (relation == 2 && a == b) )
        {
            result++;
            continue;
        }
        else {
            if ( relation == 1 )
            {
                //判断a是否吃b  或者 b是否吃a 
                //即 第一层中的a是否与第二层中的b属于同一并查集 或 第三层中的b 和第一层中的a是否属于同一并查集
                if (find(a) == find(n + b) || find(a) == find(n * 2 + b))
                {
                    result++;
                    continue;
                }
                else {
                    merge(a,b);
                    merge(a+n,b+n);
                    merge(a + 2 * n, b + 2 * n);
                }
            }
            else {
                //判断a是否与b同类 或者 b是否吃a
                if (find(a) == find(b) || find(n * 2 + b) == find(a))
                {
                    result++;
                    continue;
                }
                else {
                    merge(a,n+b);
                    merge(a + n, b + 2*n);
                    merge(a + 2 * n, b);
                }
            }
        }
    }
    cout << result;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/yjr3426619/article/details/81043253
今日推荐