POJ 1182 食物链

题目描述

有三类动物ABC,如图 1-1所示,A吃B,B吃C,C吃A。给出N个动物和K个动物之间的关系,判断有多少局是假的,即与其他的话有明显的矛盾。例:A吃A与B吃A,则为明显的矛盾。


图 1-1 食物链关系描述

算法设计

使用带权并查集(如果对并查集不了解,可以参考并查集),将确定了关系的节点储存在一棵树中,没有确定关系则在不同的树中。权重表示该节点与父节点的关系,如对于动物X和动物Y,假如在树中X为Y的父亲。
由图 1-1可知,X和Y的关系只有以下三种

  • 如果XY为同类,则Y对应的权重为0
  • 如果X吃Y,则Y对应的权重为1
  • 如果Y吃X,则Y对应的权重为2

关系更新如下所示

  • 查找时关系更新
    假设查找前后如图 1-2所示,根据z与x的关系不变,则有关系式rz’=(ry+rz)%3。

    图 1-2查找关系更新图解
  • 合并时关系更新
    如图 1-3所示,记fx为x的父节点,fy为y的父节点,根据合并前后关系不变则有(rfy+ry) mod 3=(rx+r-1) mod 3即rfy=(rx+r-ry+2) mod 3,这里加2的原因是采用0、1、2表示关系,输入r则为1、2、3.

    图 1-3 合并关系更新图解
  • 判断是否满足关系
    如果输入的两个元素已经在同一个不相交集中,那么需要判断输入与之前的关系是否产生矛盾,如图1-4所示,根据关系不会改变的特性,可以推知关系式为rx+r-1=ry,即(r-1)==(r[y]-r[x]+3)%3,根据该关系进行判断。

    图 1-4 判断关系图解
    伪代码描述
find(a)
    if(a != father[a])
        t = father[a]
        father[a] = find[t]
        r[a] = (r[a] + r[t]) % 3
    return father[a]
uinon(a,b,r)
    fa = find(a)
    fb = find(b)
    father[fb] = fa
    r[fb] = (r[a] + r – r[b] + 2)%3
    judge(r,x,y)
    if(x > N || y > N){
        count ++
    }
    else if(x == y && r != 1){
        count ++
    }
    else{
        fx = find(x)
        fy = find(y)
        if(fx == fy){
        if(r – 1 != (r[y] – r[x] + 3 )%3){
            count ++}
        else{
        union(x,y)
        }
    }
代码实现
//
// Created by kiff on 17-11-19.
//
#include <stdio.h>
int father[50001];
/*the relation to father
 0: the same species
 1: father eats i
 2: i eats father*/
int relation[50001];
int find(int search);
void Union(int a,int b,int r);
int main(){
    //initial the union find
    for (int i = 0; i < 50001; ++i) {
        father[i] = i;
        relation[i] = 0;
    }
    int N,K,d,x,y;
    scanf("%d%d",&N,&K);
    int count = 0;
    for (int i = 0; i < K; ++i) {
        scanf("%d%d%d",&d,&x,&y);
        if(x > N || y > N){//the input is illicit
            count ++;
        }
        else if(x == y && d != 1){
            count ++;
        }
        else{
            int fx = find(x);
            int fy = find(y);
            if(fx == fy){
                //judge if this answer is conflicted with others
if ((d-1) != (relation[y] - relation[x] + 3)%3){
                    count ++;
                }
            } else{//union the two elements
                Union(x,y,d);
            }
        }
    }
    printf("%d",count);
}
int find(int search){
    if(father[search] != search){
        int temp = father[search];
        father[search] = find(temp);
        //update the realtion between search and its father
        relation[search] = (relation[search] + relation[temp]) % 3;
    }
    return father[search];
}
void Union(int a,int b,int r){
    int father_a = find(a);
    int father_b = find(b);
    father[father_b] = father_a;
    /*update the relation between father_b and father_a
    because I use 0 1 and 2, however the input is 1 2 and 3,so I plus 2 rather than 3 to ensure it is non-negative*/
    relation[father_b] = (relation[a] + r - relation[b] + 2)%3;
}

复杂度分析

假设输入规模为n,输入数量为m,则构建并查集的复杂度为O(n),如果输入直接非法,则可以在O(1)的时间内发现,所以不需要考虑,如果和前面的由冲突那么通过find操作可以进行判断,前面说到find的摊还复杂度为α(n),如果不在同一个不相交集合,那么可以使用union进行合并,union的摊还复杂度为α(n),所以整个程序的复杂度为n+mα(n)。

编程技巧

由于之前接触到的并查集不多,且均是非带权并查集,看到该题目只是想到了添加一个辅助数组来进行关系的划分,但是不清楚如何进行表示,看了相关题解后发现可以使用0、1、2这三个数字分别表示该关系,然后进行求解,由于循环关系很方便的表示和进行判断。此外,在初始化的时候使用memset将所有节点都置为-1,比使用循环效率要高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值