Poj1182食物链 (并查集)

来源:互联网 发布:阿里云授权服务中心 编辑:程序博客网 时间:2024/04/18 16:35

题目链接:

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;}
并查集还是很神奇滴~



0 0
原创粉丝点击