POJ1182食物链 并查集 (向量偏移 讲解)

1 题意中文。

2 分析

(理解挺简单的,不过在学习这个的时候没看到讲解的让我觉得特别好的Blog,从自己理解的角度出发,为后来者献上一份绵薄之力,不论写得好不好、不欢迎转载哦:)

关键理解两点:

a.

集合不再是指同一类,该集合的root也不仅仅是普通意义(起标志作用的)的代表元;

新的概念凭借这样一种观点:凡是出场过的角色,互相之间都有关系,不论关系是什么(吃?被吃?同一类?)都无所谓,只要有直接关系或者间接关系便放入一个集合,而一个集合的root,不单单起到该集合的标志性作用,而且作为一个集合中所有元素之间关系的桥梁。因此,该集合内任意两个元素都可以通过比较自己与该集合的root的关系,来得知自己与对方之间的关系应该是什么。

可以看出,集合不再是同一类(有同一性质)元素的容器,而成为了互相之间有关系的一类元素的容器,但是依然没有脱离并查集的含义。

b.

数据结构是这样一个结构体数组,它包含两个域:一个值代表其父节点是谁,另一个值代表自己与父节点的关系如何。

struct Node{

int parent;

int relation;

}node[maxn];

而在 并:Join()、查:Find()以及main中判断结果,就是建立并维护以及查询各自的parent、relation,而在维护relation的过程,就是一个简单的向量运算的过程:

I).

对于Find()中的转移公式,以1.par=2 , 2.par=3 为例:

当需要更新1.par=3,也需要更新1.rel,使其由关于1和2的关系变为1和3的关系:

在code中,即

int Find(int x){
    if(x==node[x].par)  return x;
    else{
        int old_par=node[x].par;
        node[x].par=Find(node[x].par);
        node[x].rel=(node[x].rel+node[old_par].rel+3)%3;//Because of recursion,it is always update before it is now.
        return node[x].par;
    }
}

其中,对于更长的关系链:

也是递归进去再回溯出来,一层一层利用[父节点和root的关系] 和 [自己和父节点的关系] 得到 [自己和root的关系]:


II).

对于Join()中的转移公式,以1.par=2 , 3.par=4 为例,当输入d,1,3 (根据题意知:d==1 || d==2),那么因为两个圈子的人之前没有交集,现在产生了交集,于是合并成一个新的圈子,让2.par=3,依然重点关注,2.rel如何更新:(因为4依然是root,所以4.rel=4,而2由之前的2.rel发生改变)

 

在题目中输入的d是2时,意味着1和3是吃的关系,而在程序我们定义d'==1代表这样的关系,所以d-1;当输入d=1,在题目中代表输入的两者是同类时,因为我们的程序中把0作为同类的关系,所以也是d-1。

于是得到公式:

 node[px].rel=(-node[x].rel+node[y].rel+(d-1)+3)%3;

其中,因为有-,为了避免负数出现,所以(...+3)%3,使得到的结果在0~2,0是同类,1是x吃y,2是x被y吃。


III).

对于main()中的转移公式,用以判断结果,以1.par=3 , 2.par=3,为例,我们想知道1和2的关系是什么?而3就是这个集合中的root,是集合中各个元素的关系的桥梁:

从而得到的数表示的关系(?==0  说明1和2同类,?==1  说明1吃2,?==2  说明1被2吃),来与输入的d(d==1,1和2同类;d==2,1吃2)进行判断。

Code:

else if(d==2&&( (node[x].rel+(-node[y].rel)+3)%3!=1) ){
                    ans++;


3

#include <iostream>
#include <stdio.h>
#include <algorithm>

using namespace std;

const int maxn=5e4+10;
int ans,n,k;
struct Node{
    int par;// a. Usually ,the directional ancestor ; b. while do "Find()", it's par will be it's top ancestor.
    int rel;// the relation with it's ancestor.
}node[maxn];
void Ini(){
    ans=0;
    for(int i=1;i<=n;i++){
        node[i].par=i;
        node[i].rel=0;
    }
}
int Find(int x){
    if(x==node[x].par)  return x;
    else{
        int old_par=node[x].par;
        node[x].par=Find(node[x].par);
        node[x].rel=(node[x].rel+node[old_par].rel+3)%3;//Because of recursion,it is always update before it is now.
        return node[x].par;
    }
}
void Join(int x,int y,int d){
    int px,py;
    px=Find(x);
    py=Find(y);
    node[px].par=py;
    node[px].rel=(-node[x].rel+node[y].rel+(d-1)+3)%3;
}
int main()
{
    //while(~scanf("%d %d",&n,&k)){
        scanf("%d%d",&n,&k);
        Ini();
        int d,x,y;
        while(k--){
            scanf("%d %d %d",&d,&x,&y);
            if(x>n||y>n||(d==2&&x==y)){
                ans++;
                continue;
            }
            int px=Find(x);
            int py=Find(y);
            if(px!=py){
                Join(x,y,d);
            }
            else{
                if(d==1&&(node[x].rel!=node[y].rel))
                    ans++;
                else if(d==2&&( (node[x].rel+(-node[y].rel)+3)%3!=1) ){
                    ans++;
                }
            }
        }
        cout<<ans;
    //}
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值