Poj1182食物链 (并查集)

题目链接:

http://poj.org/problem?id=1182

题意:

最多n个动物,每个都有可能是A,B,C三种中的一种,k句话,两种类型:

1.1 x y 代表x与y是用一种

2.2 x y 代表x吃y

输出谎话的数目。

两种做法都是并查集:

第一种:因为每个动物都是这三种的一种,所以为每个动物初始化三种可能x-A x-B x-C分别代表x这个动物属于A,B,C种。用并查集来维护这些关系,在同一个集合中的元素具有相同的真假性质

对于1 x y:检查x与y+n是否在同一个集合和x与y+2n是否在同一个集合 如果有一个为真,表明x与y不是同类,更新结果,否则分别合并(x,y)(x+n,y+n)(x+2n,y+2n),表明x与y要么都属于A要么都属于B要么都属于C。

对于2 x y:检查x与y是否在同一个集合和x与y+2n是否在同一个集合 如果一个为真,表明x与y不是x吃y的关系,更新结果,否则分别合并(x,y+n)(x+n,y+2n)(x+2n,y),表明x吃y,要么为AB要么为BC要么为CA。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 50000*3+5;
int fa[maxn],height[maxn];//fa[i]=i 代表i属于A种类  fa[i+n]代表i属于B种类 fa[i+2n]代表i属于C种类
void ini(int n)
{
    for(int i=1;i<=3*n;i++)
    {
        fa[i]=i;
        height[i]=0;
    }
}
int findfa(int x)
{
    if(fa[x]==x) return x;
    return fa[x]=findfa(fa[x]);
}
void unite(int x,int y)
{
    x=findfa(x);
    y=findfa(y);

    if(x==y) return ;
    if(height[x]<height[y])
        fa[x]=y;
    else
    {
        fa[y]=x;
        if(height[x]==height[y]) height[x]++;
    }
}
bool same(int x,int y)
{
    if(findfa(x)==findfa(y)) return true;
    return false;
}
int main()
{
    //freopen("in.txt","r",stdin);
    int n,k,res;
    scanf("%d%d",&n,&k);
    {
     res=0;
     ini(n);
     for(int i=1;i<=k;i++)
     {
         int d,x,y;
         scanf("%d%d%d",&d,&x,&y);
         if(x>n||y>n||x<=0||y<=0) {res++;continue;}
         if(d==1)
         {//x与y同种
             if(same(x,y+n)||same(x,y+2*n)) //矛盾
                res++;
             else
             {
                 unite(x,y);
                 unite(x+n,y+n);
                 unite(x+2*n,y+2*n);
             }
         }
         else
         {//x吃y
             if(same(x,y)||same(x,y+2*n)) //如果x在A组且y在A组 或者 x在A组y在C组 则矛盾
                res++;
             else
             {
                 unite(x,y+n);
                 unite(x+n,y+2*n);
                 unite(x+2*n,y);
             }
         }
     }
     cout<<res<<endl;
    }
    return 0;
}

第二种:带权并查集。增添一个数组r[i]代表i与其父节点之间的关系,定义0为同类,1为i被fa[i]吃,2为i吃fa[i],关键就是如何维护r[i]数组了。如果两个动物不在一个集合中,表示他们之间的关系还没有定义,直接进行合并操作。如果我们知道1->2和2->3的关系,如何推出1->3的关系,通过列表归纳(看别人题解- -)可以得到如果一个点为a,父节点为fa,爷爷节点为ffa,那么a->ffa=(a->fa+fa->ffa)%3即 r[a]=(r[a]+r[fa])%3,有了这个关系,合并集合的时候就可以推导出那个关系了。具体看代码。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 50005;
int fa[maxn],r[maxn];//fa[i]代表i的父节点 r[i]代表i与父节点的关系 0同种 1被父节点吃 2吃父节点
void ini(int n)
{
    for(int i=1;i<=n;i++)
    {
        fa[i]=i;
        r[i]=0;//自己与自己是同种
    }
}
int findfa(int a) //返回值为a的父节点
{
    if(fa[a]==a) return a;
    int faa=fa[a]; //每次对fa[a]递归前先把它记录下来 因为回溯过程中fa[a]已经路径压缩 而递推关系的时候又需要fa[a]
    fa[a]=findfa(fa[a]);
    r[a]=(r[faa]+r[a])%3;  //递推关系
    return fa[a];
}
void unite(int a,int b,int d) //合并a与b的关系
{
    int faa=findfa(a),fab=findfa(b);//得到的过程中已经进行了路径压缩 即r[a]就是a与它的树根的关系 r[b]就是b与它的树根的关系
    if(faa==fab) return ;
    fa[fab]=faa;
    r[fab]=(r[a]+3-r[b]+d-1)%3;  //递推出新的关系
}
int main()
{
    //freopen("in.txt","r",stdin);
    int n,k,res;
    scanf("%d%d",&n,&k);
    ini(n);
    res=0;
    for(int i=1;i<=k;i++)
    {
        int d,x,y;
        scanf("%d%d%d",&d,&x,&y);
        if(x>n||y>n||x<=0||y<=0) {res++;continue;}
        if(d==2&&x==y) {res++;continue;}
        int faa=findfa(x),fab=findfa(y);
        if(d==1&&faa==fab&&r[x]==r[y]) continue;           //在一个关系树中且是同一类
        if(d==2&&faa==fab&&(1+r[x])%3==r[y]) continue;     //在一个关系树中且是x吃y的关系
        if(faa!=fab)                                       //若不在一个关系树中 则不可能出现矛盾 合并关系树 推导出正确的关系
            unite(x,y,d);
        else
            res++;                                         //不符合上面的所有情况则发生了矛盾
    }
    cout<<res<<endl;
    return 0;
}
并查集还是很神奇滴~



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值