[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

 

posted @ 2019-08-31 15:09  QUEKI嶺冬  阅读(1617)  评论(0编辑  收藏  举报
/*! Color themes for Google Code Prettify | MIT License | github.com/jmblog/color-themes-for-google-code-prettify */ .pln{color:#4d4d4c}ol.linenums{margin-top:0;margin-bottom:0;color:#8e908c}li.L0,li.L1,li.L2,li.L3,li.L4,li.L5,li.L6,li.L7,li.L8,li.L9{padding-left:1em;background-color:#fff;list-style-type:decimal!important;}@media screen{.str{color:#718c00}.kwd{color:#8959a8}.com{color:#8e908c}.typ{color:#4271ae}.lit{color:#f5871f}.pun{color:#4d4d4c}.opn{color:#4d4d4c}.clo{color:#4d4d4c}.tag{color:#c82829}.atn{color:#f5871f}.atv{color:#3e999f}.dec{color:#f5871f}.var{color:#c82829}.fun{color:#4271ae}} /*下面是我设置背景色,字体大小和字体*/ .cnblogs-markdown code{ background:#fff!important; } .cnblogs_code,.cnblogs_code span,.cnblogs-markdown .hljs{ font-size:16px!important; } .syntaxhighlighter a, .syntaxhighlighter div, .syntaxhighlighter code, .syntaxhighlighter table, .syntaxhighlighter table td, .syntaxhighlighter table tr, .syntaxhighlighter table tbody, .syntaxhighlighter table thead, .syntaxhighlighter table caption, .syntaxhighlighter textarea { font-size: 16px!important; } .cnblogs_code, .cnblogs_code span, .cnblogs-markdown .hljs{ font-family:consolas, "Source Code Pro", monaco, monospace !important; } //以上是代码高亮 /* 文字特效 */