zoukankan      html  css  js  c++  java
  • [NOI2001]食物链(并查集)


    题目描述

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

    一行,一个整数,表示假话的总数。

    输入输出样例

    输入 #1 
    100 7
    1 101 1
    2 1 2
    2 2 3
    2 3 3
    1 1 3
    2 3 1
    1 5 5
    
    输出 #1 
    3
    

    说明/提示

    1 ≤ N ≤ 5 ∗ 10^4

    1 ≤ K ≤ 10^5


    多种解法,就我所知:

    ①多集(开三个并查集)

    ②取模


    最开始我开了三个并查集水果,分别代表自己、自己的猎物、自己的天敌(吃自己的)

    在这个过程中不仅感叹:思维差异影响代码复杂度啊!(哭死在RE的血泪中)

    看了代码估计就都明白了

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 100005;
    int n,m,fa[N*3],ans,s,a,b;
    int find(int x)
    {
        if(fa[x]==x)return fa[x];
        return find(fa[x]);
    }
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=n*3;i++) fa[i]=i;
        for(int i=1;i<=m;i++){
            cin>>s>>a>>b;
            if(a>n||b>n){
                ans++;
                continue;
            }
    int t1=find(a),t2=find(b),t3=find(a+n),t4=find(b+n),t5=find(a+n+n),t6=find(b+n+n);
            if(s==1){  //声明他们是同类 
                if(t1==t4||t2==t3)ans++;  //互为食物 
                else {
                    fa[t1]=t2; //合并 
                    fa[t3]=t4;
                    fa[t5]=t6;
                }
            }
            if(s==2){  //声明a吃b 
                if(t1==t2||t1==t4)ans++;  //ab同类或b吃a 
                else  fa[t3]=t2,fa[t5]=t4,fa[t1]=t6; //合并 
            }
        }  
        cout << ans;
        return 0;
    }
    View Code

    ②带权并查集

    以下转自luogu天泽龟大佬的题解↓
    (先%%一下题解大佬orz)

    带权并查集的诠释是这样的:

    在对并查集进行路径压缩和合并操作时,这些权值具有一定属性,即可将他们与父节点的关系,变化为与所在树的根结点关系。

    也就是说,权值代表着当前节点与父节点的某种关系(即使路径压缩了也是这样),通过两者关系,也可以将同一棵树下两个节点的关系表示出来。

    而P2024《食物链》这道题,又属于加权并查集下的分支:

    种类并查集

    由题意得,动物一共只有A,B,C三种,也就是说只要确定了一种动物的种类和他们的关系(即权值),其他的动物的种类也就知道了。

    我们用re数组表示编号i与父亲节点的权值关系,由于只有三种动物,所以权值也只有三种:0-->同种动物,1-->捕食关系,2-->被捕食关系,转移时便可以采用对3取模来实现。(初始化为0,即自己与自己为同种动物)

    那么第一个问题,就是如何在查找与合并时转移这种权值?

    1.合并:并查集合并的本质就是一棵树认另一棵树做父亲,把树根相连即可,但是能否也把权值直接赋值呢(比如1操作就直接赋值为1)? 当然不行,因为给你的a,b是树下节点,还有考虑各自与树根的关系。 也就说,推出A,B各自与根的关系,就可以实现树根权值的连接了。

    设F1与F2分别为A,B的根,两者权值关系为re[F1],A与F1的权值关系是re[A],B与F2的权值关系是re[B],A与B的权值关系为x。

    由图得,re[f1]=x+re[b]-re[a]

    由于可能会造成re[b]-re[a]<0的情况,所以加3再对三取模。又因为x已知为0或1(要么是同种动物,要么是捕食关系),所以最终结果为:

    re[f1]=(re[b]-re[a]+3)%3或re[f1]=(1+re[b]-re[a]+3)%3;

    2.查找(路径压缩):路径压缩就是在搜索的时候找到最远的祖先,然后将父亲节点赋值,对于权值而言,就是找出权值与最远祖先之前所有边权传递的过程,找出节点与父亲节点的关系,依次传递即可。

    设在同一树内,3号节点父亲是2号,2号父亲是根1号。与父亲的关系依次为re[3],re[2],路径压缩后权值为re[3]撇。

    显然,re[3]撇=(re[3]+re[2])%3,别忘了取模。

    当然不会数学推得话打表也是好方法,本蒟蒻就是打完表水过题再数学证明的(:з」∠)


    这两式子一出来,题目就好做多了(我还是因为板子打错了改个近一小时)

    根据题目我们还可以确定:判断两点的关系是否正确必须要在同一棵树下,反之则一定正确。(因为如果是两棵树,两点的关系就不能确定了。)

    然后一些小问题又没啥好说了,贴上丑陋的代码:

    #include <iostream>  
    using namespace std;
    int f[100000],re[100000];  //0-->同种动物,1-->捕食关系,2-->被捕食关系。
    int n,m,a,b,p,ans=0;
    int find(int a)
    {
        int fa = f[a];
        if (a != fa) {
            f[a] = find(fa);
            re[a] = (re[a] + re[fa]) % 3;     //路径压缩后的权值 
            return f[a];
        }
        else return fa;
    }
    int main()
    {
        cin >> n >> m;
        for (int i = 1; i <= n; i++) f[i] = i, re[i] = 0;  //初始化 
        for (int i=1;i <= m;i++){
            cin>>p>>a>>b;
            if ((a>n||b>n)||(p==2&&a==b)) {   
                ans++; continue;
            }
            int f1 = find(a),f2 = find(b);
            if (p == 1) { //1xy表示两者是同类 
                if (f1 == f2 && re[a] != re[b]) { //f判断是否在同一棵树
                                            //re(权值)两者是否为同种动物。 
                    ans++; continue;
                }
                else if(f1!=f2){ 
                    f[f1] = f2; 
                    re[f1] = (3 - re[a] + re[b]) % 3;   //合并权值,归入同一棵树下 
                }
            }
            if (p == 2) { //2xy  x吃y 
                if (f1 == f2) {
                    int rela = (re[a] -re[b] + 3) % 3;  //用两个节点与父亲的关系推出两者关系 
                    if (rela != 1) {
                        ans++; 
                        continue;
                    }
                }
                else 
                {   f[f1]=f2;
                    re[f1]=(3-re[a]+re[b]+1)%3;  }
            }
        }
        cout<<ans;
        return 0;
    }
    View Code
    满堂花醉三千客,一剑霜寒十四州
  • 相关阅读:
    leetcode 75 颜色分类 A
    leetcode525连续数组 A
    WCF无身份验证配置
    三读设计模式
    EntityFrameWork+Oracle学习笔记搭配环境(一)
    EntityFrameWork+Oracle学习笔记DBfirst(二)
    用Python解答百度测试开发算法面试题
    Python实现采集wordpress整站数据的爬虫
    吾八哥学Python(六):运算符与表达式
    吾八哥学Python(四):了解Python基础语法(下)
  • 原文地址:https://www.cnblogs.com/phemiku/p/11439091.html
Copyright © 2011-2022 走看看