POJ 1182 食物链

这题用并查集来写,且树的层数不超过2,3层最多只持续一个循环就会变成2层,因为只有三种动物,两两之间的关系只需要2层树就可以表示清楚了,那么现在当树有三层时怎么变成2层树呢?

先假设0,1,2分别为该结点与父结点表示的关系,0表示同类,1表示该结点被父结点吃,2表示该结点吃父结点。

这个表示方法是有原因的,目的是为了下面方便推公式,首先题目给出d表示关系,d==1表示同类,d==2表示y被x吃,那么的d-1==0,刚好表示同类,d-1==1刚好表示该结点被父结点吃(当d==2时,把x作为父结点)。

那么怎么算第三层与第一层结点的关系,并且把第三层上的结点变成第二层呢?

首先给出公式:(re[a]+re[pa[a]])%3==子结点对爷爷结点的关系;

a表示子结点,pa[a]表示父亲结点,这个公式是通过穷举法得出的,其中re数组表示该结点与父结点的关系,求出这个关系那么就可以把子结点挂到爷爷结点下面,就是把第三层结点放到了第二层上。

公式是这样推的  <span style="white-space:pre">	</span>       父亲  儿子       儿子对爷爷的关系


  1. 0      0       (i + j)%3 = 0 
  2.                0      1       (i + j)%3 = 1 
  3.                0      2       (i + j)%3 = 2 
  4.                1      0       (i + j)%3 = 1 
  5.                1      1       (i + j)%3 = 2 
  6.                1      2       (i + j)%3 = 0 
  7.                2      0       (i + j)%3 = 2 
  8.                2      1       (i + j)%3 = 0 
  9.                2      2       (i + j)%3 = 1 

现在单个结点并到一个集合中的方法已经有了,那就要考虑集合并集合了,如果题目给出x,y的关系,并且x与y在之前并没有关系且x与y所属不同的集合,那么就不能直接把y的根结点并到x的根结点上,因为你并不知道这两个根结点之间的关系,那么现在的首要问题就是要求出根结点a(x),b(y)之间的关系。

方法如下:我们假设把y结点接到x结点的下方,再把b结点(也就是y的根结点)接到y的下方,那么根据(re[y]+re[b])%3来求出b结点与x结点的关系,在用(re[x]+re[b])%3来求出b结点与a结点的关系(此时的re[b]已经被更新过了),那样的话b结点就可以接到a结点的下方。

那么问题又来了,如何求出最开始的re[b]呢,也就是b结点与y的关系。

这里用的也是穷举法,得出勇士父结点与子结点的关系就是:re[父对子]=(3-re[子对父])%3。

这个很快就可以穷举出,这里就不穷举,那么通过这个公式就可以把上面求b与a关系的两个式子整理为一个式子,(d-1+3-re[y]+re[x])%3==b结点对a结点的关系

其中d-1==re[y]这个式子在一开始说三个标志数取法的时候就已经给出,3-re[y]则是b对y的关系。

这样就可以把两个集合并起来了。

然后是判断是否说的是假话,如果a,b结点不同,那这是之前没有表明过a与b的关系,为真话,那么就把它们所属的集合并起来。

如果a与b相同,如果d==1那么只要判断re[x]是否等于re[y]就可以了,因为是二层树,所以相等为真话,不相等为假话。

如果d==2,那么需要需要把根结点当做x的子结点,然后根据公式(re[y]+3-re[x])%3是否为1即可,如果为1,则真话,不为1则为假话。

思路来源:http://blog.csdn.net/c0de4fun/article/details/7318642/

代码:

//
//  main.cpp
//  Richard
//
//  Created by 邵金杰 on 16/7/23.
//  Copyright © 2016年 邵金杰. All rights reserved.
//


#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=50000+100;
int pa[maxn],re[maxn];
int cnt=0;
int getroot(int a)
{
    if(pa[a]==a) return a;
    int t=getroot(pa[a]);
    re[a]=(re[pa[a]]+re[a])%3;
    pa[a]=t;
    return pa[a];
}
void Union(int x,int y,int a,int b,int d)
{
    pa[b]=a;
    re[b]=(d-1+3-re[y]+re[x])%3;
}
int main()
{
    int n,k;
    int d,x,y;
    scanf("%d%d",&n,&k);
    for(int i=0;i<=n;i++){
        pa[i]=i;re[i]=0;
    }
    for(int i=0;i<k;i++)
    {
        scanf("%d%d%d",&d,&x,&y);
        if(x>n||y>n) {cnt++;continue;}
        if(d==2&&x==y) {cnt++;continue;}
        int a=getroot(x);
        int b=getroot(y);
        if(a!=b) Union(x,y,a,b,d);
        else{
            if(d==1&&re[x]!=re[y]) cnt++;
            if(d==2&&(re[y]+3-re[x])%3!=1) cnt++;
        }
    }
    cout<<cnt<<endl;
}



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值