食物链(POJ - 1182)带权并查集 种类并查集

本题学习到了带权并查集的神奇之处!

在网上看到了各位前辈对此题的精妙的解析,不由得感叹连连。尤其是各位前辈的认真,让我也忍不住想要把这个题用我自己的方式再融会贯通一遍。


(一)各个变量的定义

d: 表示y对x的关系。

node[i].op:定义为该节点与其父节点的关系。

0 - 表示该节点与其父节点为同类。

1 - 表示该节点被其父节点吃。

2 - 表示该节点吃其父节点。



(二)Find() 函数

int Find(int x)
{
    if(x == node[x].par) return node[x].par;
    int tmp = node[x].par;
    node[x].par = Find(node[x].par);
    node[x].op = (node[x].op + node[tmp].op) % 3; //***
    return node[x].par;
}

标***处该如何理解呢?

即已知 儿子对父亲的关系,父亲对爷爷的关系,求儿子对爷爷关系。

(一共有9种情况)                      [该过程是A吃B,B吃C,C吃A]

               i      j 
              儿子   父亲       儿子对爷爷 
               0      0       (i + j)%3 = 0 
               0      1       (i + j)%3 = 1 
               0      2       (i + j)%3 = 2 
               1      0       (i + j)%3 = 1 
               1      1       (i + j)%3 = 2 //***
               1      2       (i + j)%3 = 0 //***
               2      0       (i + j)%3 = 2 
               2      1       (i + j)%3 = 0 //***
               2      2       (i + j)%3 = 1 //***
对于以上四个标***处,比较难理解,我画了图。

其箭头表示箭头始端吃箭头末端。蓝色箭头表示已知,红色箭头表示后推理。

 




(三)Union()函数

void Union(int x,int y,int rootx, int rooty,int d)
{
    node[rooty].par = rootx;
    node[rooty].op = (3 - node[y].op + d-1 + node[x].op) % 3;//***
}

合并过程文字描述:

假设都是2层。把node[y],先连接到node[x]上,再把node[y]的根节点node[ry]移动到node[y]上,

最后,把node[Y]的根节点移动到node[X]的根节点上,这样算op。

可以看作把y接到x上,也就说,现在x是y的父亲,y原来的根节点ry现在是y的儿子 相加求node[ry]与node[x]的op了

再加上x对其根节点rx的关系,就是ry对rx的关系。




(四) 附代码


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<stack>
#include<queue>
#include<cstring>
#include<string>
#include<set>
#include<cmath>
#include<map>
#include<list>
#include<sstream>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long LL;
const int maxn = 50000 + 8;

int n,k,a,b,c,ans;
struct Node
{
    int par;
    int op;//0:该节点与父节点同类 1:该节点被父节点吃 2:该节点吃父节点
}node[maxn];

void Init()
{
    ans = 0;
    for(int i = 1; i <= n; ++i){
        node[i].par = i;
        node[i].op = 0;
    }
}

int Find(int x)
{
    if(x == node[x].par) return node[x].par;
    int tmp = node[x].par;
    node[x].par = Find(node[x].par);
    node[x].op = (node[x].op + node[tmp].op) % 3;
    return node[x].par;
}

void Union(int x,int y,int rootx, int rooty,int d)
{
    node[rooty].par = rootx;
    node[rooty].op = (3 - node[y].op + d-1 + node[x].op) % 3;
}
int main()
{
    scanf("%d%d",&n,&k);
    Init();
    while(k--){
        scanf("%d%d%d",&a,&b,&c);
        if(b > n || c > n){
            ans++;
            continue;
        }
        if(a == 2 && b == c){
            ans++;
            continue;
        }
        int x = Find(b);
        int y = Find(c);
        if(x != y) Union(b,c,x,y,a);
        else{
            if(a == 1 ) {
                if(node[b].op != node[c].op)ans++;
            }
            else if(a == 2){
                if( (node[c].op + 3 - node[b].op) % 3 != 1 ) ans++;
            }
        }
    }
    cout << ans << endl;
}

/*
100 10
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
2 3 4
2 4 3
1 2 3
*/

方法二:种类并查集

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<stack>
#include<queue>
#include<cstring>
#include<string>
#include<set>
#include<cmath>
#include<map>
#include<list>
#include<sstream>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long LL;
const int maxn = 50000 + 8;

int n,k,d,x,y,ans;
int rrank[maxn],par[maxn];

void Init()
{
    memset(rrank,0,sizeof rrank);
    memset(par,0,sizeof par);
    ans = 0;
    for(int i = 1; i <= n; ++i){
        rrank[i] = 0;
        par[i] = i;
    }
}
int Find(int x)
{
    if(x == par[x]) return x;
    int fa = par[x];
    par[x] = Find(par[x]);
    rrank[x] = (rrank[x] + rrank[fa]) % 3;
    return par[x];
}
bool Union(int x,int y,int rx,int ry,int d)
{
    if(ry == rx) {
        if (d == 1 && rrank[x] != rrank[y]) return false;
        if (d == 2) {
            /*if (rrank[y] == 1 && rrank[x] != 0) return false;
            if (rrank[y] == 2 && rrank[x] != 1) return false;
            if (rrank[y] == 0 && rrank[x] != 2) return false;*/
            if( (rrank[y] + 3 - rrank[x]) % 3  != 1) return false;
        }
        return true;
    }

    if(ry != rx) {
        par[ry] = rx;
        rrank[ry] = (3 - rrank[y] + d - 1 + rrank[x]) % 3;
    }
    return true;
}
int main()
{
    scanf("%d%d",&n,&k);
    Init();
    while(k--)
    {
        scanf("%d%d%d",&d,&x,&y);
        if(x > n || y > n || (d == 2 && x == y) ){
            ans++;
            continue;
        }
        int a = Find(x);
        int b = Find(y);
        if(!Union(x,y,a,b,d)) {
            ans++;
            continue;
        }
    }
    cout << ans << endl;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值