zoukankan      html  css  js  c++  java
  • 1074 食物链

    1074 食物链

     

    2001年NOI全国竞赛

     时间限制: 3 s
     空间限制: 64000 KB
     题目等级 : 钻石 Diamond
     
     
     
    题目描述 Description

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

    输入描述 Input Description

    第一行是两个整数N和K,以一个空格分隔。   

    以下K行每行是三个正整数D,X,Y,两数之间用一个空格隔开,其中 D 表示说法的种类。   

    若D=1,则表示X和Y是同类。   

    若D=2,则表示X吃Y。

    输出描述 Output Description

    只有一个整数,表示假话的数目。

    样例输入 Sample Input

    100 7

    1 101 1

    2 1 2

    2 2 3

    2 3 3

    1 1 3

    2 3 1

    1 5 5

    样例输出 Sample Output

    3

    数据范围及提示 Data Size & Hint

    输入文件  

     对7句话的分析 100 7

    1 101 1  假话

    2 1 2    真话

    2 2 3    真话

    2 3 3    假话

    1 1 3    假话

    2 3 1    真话

     1 5 5    真话

    NOI 2001 食物链(eat)

    分类标签 Tags 点此展开 

     
     
    加权并查集的经典例题
     
    方式1:
    思路:

    对于每个点,除去记录并查集中的根节点外,还要记录下该点与父节点的关系:
    r[x]=0 表示x与其根节点为同类
    r[x]=1 表示x的父节点吃x
    r[x]=2 表示x吃x的父节点
    而对于并查集的合并操作中,要进行以下操作
    f[fy]=fx;
    r[fy]=(3+(z-1)+r[x]-r[y]) mod 3;(fx为x的根节点 fy为y的根节点 z为x、y之间的关系)

    #include<cstdio>
    int n,m,ans,r[50010],fa[50010];//可以减半 
    int find(int x){//并查集稍作修改
        if(fa[x]==x) return x;
        int tmp=fa[x];//这是x上次的祖先
        fa[x]=find(fa[x]);
        r[x]=(r[x]+r[tmp])%3;//用他和他上次的祖先的关系and他上次的祖先和现在的祖先的关系推出现在他和他现在的祖先的关系
        return fa[x];
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) fa[i]=i;
        for(int i=1,x,y,z;i<=m;i++){
            scanf("%d%d%d",&z,&x,&y);
            if(x>n||y>n){ans++;continue;}
            int fx=find(x),fy=find(y);
            if(z==1)
                if(fx==fy) ans=r[x]!=r[y]?ans+1:ans;
                else fa[fy]=fx,r[fy]=(r[x]-r[y]+3)%3;//推出y与x的关系 
            else
                if(fx==fy) ans=(r[x]+1)%3!=r[y]?ans+1:ans;
                else fa[fy]=fx,r[fy]=(r[x]-r[y]+3+1)%3;
        }
        printf("%d
    ",ans);
        return 0;
    }
    方式2:
    思路:
    题目要求我们判假,如果我们用并查集来做这道题,应该何从下手呢?首先,很显然的是,我们必须维护一个点与点关系的集合,通过这个关系集合来判假。原始的并查集只能用于维护“属于同一类“的这种关系,但是这道题却还要求我们维护吃的关系,这样才能针对每一个给出的论断进行判断。维护属于同一组的这种信息很容易实现,但是又如何体现捕食的关系呢?稍加思考,可以这样来转化。针对每只动物,为他们分别创建三个元素,每个元素表示不同的种类信息(具体实现为i表示这只动物是A,i+N就表示是B,i+2*N就表示这只动物是C了),这样,就用N*3个元素来建立并查集,这样,种类信息便成功加进来了。再来,就是维护了。其实很简单,虽然不知道给出的每一个论断(这里指第二种)中,两只动物分别是什么种类的,但事实上无需知道,因为食物链是一个环,随便哪一点选作A都行,只要每种动物的相对关系不变就行。所以,我们把同一个集合中的所有元素都看做符合事实,由于加上了类型信息,所以吃的关系也能判断出来。如此一来,针对每一个论断直接判断是否矛盾,不矛盾,就更新集合,否则是假话,把计数变量加一就可以了。下面是具体的实现代码。
    #include<cstdio>
    int n,m,ans,fa[300010];
    int find(int x){
        return fa[x]==x?x:fa[x]=find(fa[x]);
    }
    inline void merge(int x,int y){
        fa[find(x)]=find(y);
    }
    inline bool same(int x,int y){
        return find(x)==find(y);
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n*3;i++) fa[i]=i;
        for(int i=1,x,y,z;i<=m;i++){
            scanf("%d%d%d",&z,&x,&y);
            if(x>n||y>n){ans++;continue;}
            if(z==1)
                if(same(x,y+n)||same(x,y+n*2)) ans++;//都是判断x与其他两个不相应的是否同组 
                else merge(x,y),merge(x+n,y+n),merge(x+n*2,y+n*2);
            else
                if(same(x,y)||same(x,y+n*2)) ans++;
                else merge(x,y+n),merge(x+n,y+n*2),merge(x+n*2,y);
        }
        printf("%d
    ",ans);
        return 0;
    }
  • 相关阅读:
    javaweb消息中间件——rabbitmq入门
    virtual box 桥接模式(bridge adapter)下无法获取ip(determine ip failed)的解决方法
    Apache Kylin本地启动
    git操作
    Java学习总结
    Java中同步的几种实现方式
    hibernate exception nested transactions not supported 解决方法
    vue 中解决移动端使用 js sdk 在ios 上一直报invalid signature 的问题解决
    cookie 的使用
    vue 专门为了解决修改微信标题而生的项目
  • 原文地址:https://www.cnblogs.com/shenben/p/5622288.html
Copyright © 2011-2022 走看看