poj1182食物链(种类并查集)

题目链接:poj1182

/*http://poj.org/problem?id=1182*/
/*题意:
思路:首先,集合里的每个点我们都记录它与它这个集合(或者称为子树)的根结点的
相对关系rank[]。0表示它与根结点为同类,1表示它吃根结点,2表示它被根结点吃。
那么判断两个点x, y的关系,我们令fx = Find(x), fy = Find(y),即fx, fy分别为x,y子树
的根结点。
    1. 如果fx != fy,说明x, y暂时没有关系,那么关于他们的判断都是正确的,
然后合并这两个子树。这里是关键,如何合并两个子树使得合并后的新树能保证正确呢?
这里我们规定只能fy合并到fx. 那么合并后,rank[y]肯定要改变,那么改成多少呢?这里的
方法就是找规律,列出部分可能的情况,就差不多能推出式子了。
这里式子为 : rank[fy] = (rank[x]-rank[y]+3+d-1)%3;
这里的d为判断语句中x, y的关系。还有个问题,我们是否需要遍历整个x子树并更新每个结
点的状态呢?答案是不需要的,因为我们可以在find()函数稍微修改,即结点x继承它的父亲
(注意是前父亲,因为路径压缩后父亲就会改变),即它会继承到fx结点的改变,所以我们不需
要每个都遍历过去更新。
    2. 如果fx = fy,说明x, y之前已经有关系了。那么我们就判断语句是否是对的,
同样找规律推出式子。即if ( (rank[x]+1)%3 != rank[y] ), 那么这句话就是错误的。
    3. 再对find()函数进行些修改,即在路径压缩前纪录前父亲是谁,然后路径压缩后,
更新该点的状态(通过继承前父亲的状态,这时候前父亲的状态是已经更新的)。*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 50010;
int f[N];
int rank[N];//表示和父结点的关系(回溯路径压缩后,父结点即为根结点),0表示同类,1表示被吃,2表示吃
void init(int &n)
{
    for(int i = 1; i <= n; i ++)
        f[i] = i,rank[i] = 0;
}
int find(int x)
{
    if(x != f[x]){
        int fx = f[x];
        f[x] = find(f[x]);
        //回溯由子节点与父节点的关系和父节点与根节点的关系找子节点与根节点的关系
        rank[x] = (rank[x] + rank[fx])%3;
    }
    return f[x];
}
void Union(int x, int y, int d)
{
    int fx = find(x);
    int fy = find(y);
    f[fy] = fx;//合并树,被x吃,所以以x的根为父
    rank[fy] = (rank[x]-rank[y]+3+d-1)%3;
}
int main()
{
    int n,m,x,y,d;
    scanf("%d%d",&n,&m);
    int ans = 0;
    init(n);
    while(m --){
        scanf("%d%d%d",&d,&x,&y);
        if( x > n || y > n ||(d == 2 && x == y) ) ans ++;
        else if(find(x) == find(y)){//表示两个点有关系,即在同一颗树上
            if(d == 1 && rank[x] != rank[y]) ans ++;//不是同类
            if(d == 2 && (rank[x]+1)%3 != rank[y]) ans ++;
            //x吃y:a. rank[x] = 0, rank[y] = 1;
            //      b. rank[x] = 1, rank[y] = 2;
            //      c. rank[x] = 2, rank[y] = 0;
        }
        else Union(x, y, d);
    }
    printf("%d\n",ans);
    return 0;
}


这类题基本上就是套这个模板,其它种类并查集: poj2492     poj1703   poj1988
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值