poj1182——食物链

题目大意:设一个有三类生物ABC的食物链,现在说K句话判断假话个数,这些话有两种格式:1 X Y表示XY同类,2 X Y表示X吃Y,如果满足以下三条之一就是假话,否则是真话:当前的话与前面的某些真的话冲突;当前的话中X或Y比N大;当前的话表示X吃X。假设有N个动物。

输入:N K(1 <= N <= 50,000,0 <= K <= 100,000)

           D  X  Y(一共K行表示K句话,D=1/2)

输出:假话总数

分析:这道题是典型的并查集应用,在同一个生态圈内的生物在同一个并查集中

           relation[]数组记录i所在连通集的根与i的关系,0表示同类,1表示父吃子,2表示子吃父,初始化每个结点父节点都是自己,所以relation[i] = 0(自己和自己当然是同类)

           接下来判断真假,后面两种情况简单,下面说说如何判断当前话是否与前面的真话冲突:

           (一)xy属于同一连通分量

            1.D==1即xy同类,此时判断x根与x的关系是否与y根与y的关系相等,不等则是假话

            2.D==2即xy不同类,并且表示x吃y,所以如果这句话导致加入的xy关系是y吃x,则这句话是假话

            (二)xy属于不同连通分量

             说明之前的话没有涉及到xy的关系,所以不会存在假话的情况,直接合并两个并查集,更新relation[fy]

代码里那些式子推导参考http://blog.163.com/happyliyifan@126/blog/static/37462772201361695127793/

代码:转载自https://www.cnblogs.com/dongsheng/archive/2013/06/12/3133188.html

#include <cstdio>
#include <iostream>

using namespace std;

const int N = 50005;
int father[N];
int relation[N];//根点节到点节的关系

void init(int n)
{
    for(int i = 0; i <= n; ++i)
    {
        father[i]= i;
        relation[i] = 0;
    }
}
//更新的步调,先将当前点节与其根点节相连,然后更新其与根点节的关系
//当前节点x与根节点r的关系更新的方法:
//    (x与其父点节的关系+其父点节的关系与根点节的关系)%3
//所以在更新节点x的数据之前需要更新其父节点的数据,这是find为什么搞成递归函数的原因
//其更新的次序是从根节点开始往下,始终到当前点节x的父点节。
int find(int x)
{
    if(x != father[x])//不是根点节
    {
        int temp = father[x];
        //将当前点节的父点节设置为根点节
        father[x] = find(temp);
        //更新当前点节与根点节的关系,由x->x父和x父->父根的关系失掉x->父根的关系
        //所以在这之前必须更新其父点节与根点节的关系
        relation[x] = (relation[x] + relation[temp]) % 3;
    }
    return father[x];
}

int main()
{
    int n, m, x, y, d, fx, fy, cnt;

    while(~scanf("%d %d", &n, &m))//POJ上只要需一次入输,所以不要需while循环
    {
        cnt = 0;
        init(n);
        for(int i = 0; i < m; ++i)
        {
            scanf("%d %d %d", &d, &x, &y);
            if(x > n || y > n)
            {
                ++cnt;
                continue;
            }
            if(d == 2 && x == y)
            {
                ++cnt;
                continue;
            }
            fx = find(x);
            fy = find(y);
            if(fx == fy)//属于同一个子集
            {
                //如果x、y是同类,那么他们相对根点节的关系应该是一样的
                if(d == 1 && relation[x] != relation[y])
                    ++cnt;
                //如果不是同类,加入x与y的关系之后,x相对根点节的关系(x根->y,y->x(即3-(d-1)=2).即x根->x)应该是不变的
                //这里d=2表示x - y = 2-1=1;而y->x=3-(x->y)=3-1=2;
                if(d == 2 && relation[x] != (relation[y] + 2)%3)
                    ++cnt;
            }
            else//合并两个连通区域
            {
                father[fy] = fx;//y根的父点节更新成x根
                //(d-1)为x与y的关系,3-relation[y]是y与y的根点节的关系,注意方向,relation[x]是其根点节与x的关系
                //x根->x,x->y,y->y根:即x根->y根
                relation[fy] = (relation[x] + (d-1) + (3-relation[y])) % 3;//注意这里只更新的是fy相对于根的关系
            }
        }
        printf("%d\n", cnt);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值