POJ1182

本文探讨了如何利用带权并查集解决涉及三类动物的食物链循环问题。通过分析动物之间的关系,建立了表示同类和天敌的r[]数组,并详细阐述了find和unite函数在处理不同命令时的作用。在处理过程中,注意到当动物不在同一连通分支时需要建立关系,同时考虑了命令的真假判断。文章通过示例代码展示了整个解决方案,强调了处理特殊情况的注意事项。
摘要由CSDN通过智能技术生成

题意:三类动物A、B、C构成食物链循环,告诉两个动物的关系(同类或天敌),判断有多少个关系是和正确的冲突。

哈哈,看到这道题,一下子就注意到三类动物,之前我们研究的是两类,现在只是多了一类而已,真的是而已吗?

显然不是,如果我们依照上面的想法做,每来一对动物a b就将他们归于同一个祖先下,最后,只剩下一个连通分支。如果我们仍然设r[x]表示x与根结点的关系,那么就有r[x]=0表示与根结点同类,r[x]=1表示与根结点异类(???貌似有三个类啊??这样无法区分另外两个类啊)。

既然之前的想法不能解决问题,我们回本溯源,带权并查集的本质不就是多了各结点之间的关系吗?不妨大胆设一些关系。

想着想着,因为每来一句话都要判断其真假,所以必须要让所有结点在一个连通分支内(如果分3个分支,来了新的一对动物让你判断,如果发生前后矛盾,你将不知道是之前是错的还是现在的是错的),所以数组r[]要保留,既然是食物链,不妨设r[x]=0表示x与根结点同类,r[x]=1表示x吃根结点,r[x]=-1表示x被根结点吃。这样如果命令是1 a b时,我们便可以先判断它们是否是同一连通分支的,如果是,就判断它们各自与根结点的关系(即r[a]==r[b]),如果r[a]==r[b](即表示它们是同类),命令正确,反之说明这个命令是假话。当然,如果它们不是同一连通分支的,它们之间的关系就不好判定,命令真假也就无法确定,我们只能将两个点连入同一连通分支,即直接调用unite函数。如果命令是2 a b时(即a吃b),我们仍然先判断它们是否是同一连通分支的,如果是,我们可以穷举r[a]和r[b]的值来判断此话是否正确(若想命令正确只能是:r[a]=1,r[b]=0或r[a]=0,r[b]=-1或r[a]=-1,r[b]=1)。当然,如果不是同一连通分支的,同上,直接调用unite函数。
于是,我们可以得出下面的代码:

cin>>D>>a>>b;
 
if(D==1){
    if(find(a) == find(b))  //a和b属于同一连通分量
        if(r[a] != r[b])    //a和祖先的关系 与 b和祖先的关系 不一致
            假话数量++;     //即a和b的种类不一样
    else
        unite(a,b);
}
if(D==2){
    if(find(a)==find(b))
        if(不是正确的对应关系)
            假话数量++;
    else
        unite(a,b);
}

看到这,我们目标就有啦啦啦!!至于关系的得到就锁定在find函数和unite函数内了,详细的推导日后再说。

我怀着自信满满的心去编程,却发现有个地方忽视了。。。当命令D a b过来时,如果a和b属于同一连通分支还好说,如果不是,我们就要unite(a,b),但是unite的具体内容决定r[]的更新,所以与当前传入的D有关。

于是,得到下面的代码:

cin>>D>>a>>b;
int sum = 0;            //假话数量
if(a>n || b>n || (D==2&&x==y))    //如果节点编号大于最大编号,或者自己吃自己,说谎
    sum++; 
else if(find(a) == find(b)){
    if(D==1 && r[a]!=r[b])  //如果 x 和 y 不属于同一类 
        sum++;
    if(D==2 && (r[a]+1)%3!=r[b]) //如果a没有吃b(注意要对应unite(x,y)的情况,否则一路WA到死啊!!!)
        sum++;
}
else{
    unite(a,b,d);   //如果开始没有关系,则建立关系

于是相应的代码就有了:

#include<cstdio>
#include<iostream>
using namespace std;
const int maxn = 50000+10;

int pre[maxn];         //存父亲节点
int r[maxn];         //存与根节点的关系
//r[x]==0表示x与根结点同种类  r[x]==1代表x会被根结点吃  r[x]==2代表x会吃根结点 
int n,k;

void init()
{
    for(int i=1;i<=n;i++){
        pre[i] = i;
        r[i] = 0;            //初始每个动物都与自己同种类 
    }
}

int find(int x)
{
    if(x == pre[x])    return x;
    int t = pre[x];
    pre[x] = find(pre[x]);
    //回溯由子节点与父节点的关系和父节点与根节点的关系找子节点与根节点的关系
    r[x] = (r[x]+r[t])%3;        //此处模3 
    return pre[x];
}

void unite(int x,int y,int d)
{
    int fx = find(x);
    int fy = find(y);
    pre[fy] = fx;                //合并树 注意:被x吃,所以以 x的根为父
    r[fy] = (r[x]-r[y]+3+(d-1))%3;    //对应更新与父节点的关系
}

int main()
{
    cin>>n>>k;
    init();
    int sum = 0,D,a,b;
    while(k--){
        scanf("%d%d%d",&D,&a,&b);
        if(a>n || b>n || (a==b&&D==2)){
            sum++;
            continue;
        }
        else if(find(a) == find(b))             //如果原来有关系,也就是在同一棵树中,那么直接判断是否说谎  
        {  
            if(D == 1 && r[a] != r[b]) sum++;             //如果a和b不属于同一类  
            if(D == 2 && (r[a]+1)%3 != r[b]) sum++;     // 如果a没有吃b (注意要对应unite(a,b)的情况,否则一路WA到死啊!!!)  
        }
        else unite(a,b,D);                         //如果开始没有关系,则建立关系  
    }
    printf("%d\n",sum);
    return 0;
}

poj1182

写到这,只缺一些关系的证明了。我也不想证,怎么办??搬点别人的货吧。

——————————————食物链别人讲解————————

思路:把确定了相对关系的节点放在同一棵树中

每个节点对应的 r[]值记录他与根节点的关系:

0:同类,
1:被父亲节点吃,
2: 吃父亲节点
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值