【带权并查集 He 种类并查集 详详详详详详详hhh】经典例题

POJ 1182 食物链


带权并查集

  • 在普通并查集的基础上增加了权值,表示结点与其父亲结点之间的某种关系。路径压缩时,关系更新为结点与其祖先的关系。

定义结构体 a n i m a l [ u ] { s e l f , f a , r e l a t i o n } animal[u] \{self, fa, relation \} animal[u]{self,fa,relation},意义:结点 u u u 的标号,结点 u u u 父亲的标号,结点 u u u 和它父亲之间的关系

对于这道题,我们可以定义权值的域为{0, 1, 2},分别表示

  • 0:结点u和它的父亲属于同类
  • 1:结点u的父亲吃它
  • 2:结点u吃它的父亲

我们还可以发现,输入的判断语句:d, x, y,当d=1时,d-1=0刚好是定义的同类;当d=2时,d-1=1刚好是定义的x吃y

如何路径压缩?

我们假设x,fa,grandfa分别为自己,父亲,爷爷

有: r e l a t i o n [ 爷 爷 , x ] = ( r e l a t i o n [ 父 亲 , x ] + r e l a t i o n [ 爷 爷 , 父 亲 ] ) % 3 relation[爷爷,x]=(relation[父亲,x]+relation[爷爷,父亲]) \%3 relation[,x]=(relation[,x]+relation[,])%3

  • 穷举验证:
relation[父亲,x]relation[爷爷,父亲]relation[爷爷,x]
000
011
101
112
022
202
120
210

注意!!:一定要确保父亲的relation是对的,才能更新自己的

int find(node &x)
{
    if(x.self == x.fa) return x.self;
    int fa = x.fa;
    x.fa = find(animal[x.fa]); //得保证父亲的relation是正确的才能更新自己的
    x.relation = (x.relation + animal[fa].relation) % 3;
    return x.fa;
}
如何合并?

将x和y所在的集合合并,我们可以很容易知道 a n i m a l [ f y ] . f a = f x ; animal[fy].fa = fx; animal[fy].fa=fx; 但是 f y fy fy f x fx fx 之间的关系怎么确定呢?

如下图:
在这里插入图片描述
我们已知:
① = r e l a t i o n [ f x , x ] = a n i m a l [ x ] . r e l a t i o n ①=relation[fx,x]=animal[x].relation =relation[fx,x]=animal[x].relation
② = r e l a t i o n [ x , y ] = d − 1 ②=relation[x,y]=d-1 =relation[x,y]=d1
④ = r e l a t i o n [ f y , y ] = a n i m a l [ y ] . r e l a t i o n ④=relation[fy,y]=animal[y].relation =relation[fy,y]=animal[y].relation
③ = 3 − ④ ③=3-④ =3
穷举验证③的正确性:

relation[父亲,x]relation[x,父亲]
00
12
21

所以, ① + ② + ③ = r e l a t i o n [ f x , f y ] = a n i m a l [ f y ] . r e l a t i o n ① + ② + ③=relation[fx,fy]=animal[fy].relation ++=relation[fx,fy]=animal[fy].relation

即: a n i m a l [ f y ] . r e l a t i o n = ( a n i m a l [ x ] . r e l a t i o n + d − 1 + 3 − a n i m a l [ y ] . r e l a t i o n ) animal[fy].relation = (animal[x].relation + d - 1 + 3 - animal[y].relation) % 3; animal[fy].relation=(animal[x].relation+d1+3animal[y].relation)

void Merge(int x, int y, int fx, int fy, int d) //将集合y合并到集合x
{
    animal[fy].fa = fx;
    animal[fy].relation = (animal[x].relation + d - 1 + 3 - animal[y].relation) % 3;
}
解题思路
  • 若给定的 x 、 y x、y xy 在范围外,那么一定是假话
  • 如果 x 、 y x、y xy 所在的集合不是一个,那么就是真话,将两个集合进行合并
  • 如果 x 、 y x、y xy 在同一个集合中【同一个集合中的结点都是连接在根结点的】
  1. 如果 d = = 1 d==1 d==1,那么判断 a n i m a l [ x ] . r e l a t i o n = = a n i m a l [ y ] . r e l a t i o n ? animal[x].relation == animal[y].relation ? animal[x].relation==animal[y].relation? 如果不同,说明两个结点和根结点的关系不同,即两个结点不同类:假话
  2. 如果 d = = 2 d==2 d==2,那么判断 r e l a t i o n [ x , y ] = ( 3 − a n i m a l [ x ] . r e l a t i o n + a n i m a l [ y ] . r e l a t i o n ) % 3 = = 1 ? relation[x,y]=(3 - animal[x].relation + animal[y].relation) \% 3 == 1 ? relation[x,y]=(3animal[x].relation+animal[y].relation)%3==1? 如果 r e l a t i o n [ x , y ] ≠ 1 relation[x,y] \neq 1 relation[x,y]=1 那么 x x x y y y 是假话
    (:这里的 ( 3 − a n i m a l [ x ] . r e l a t i o n ) (3 - animal[x].relation) (3animal[x].relation) 就是根结点相对于 x x x 的关系。跟上图:边③的关系道理一样~

写完撒花~,花花花花!

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

int read()
{
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') { if (c == '-') f = -f; c = getchar(); }
    while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

const int maxN = 50004;

int n, k, ans;
struct node{
    int self, fa, relation;
}animal[maxN];

void init()
{
    for(int i = 0; i <= n; ++ i )
        animal[i] = node{i, i, 0};
}

int find(node &x)
{
    if(x.self == x.fa) return x.self;
    int fa = x.fa;
    x.fa = find(animal[x.fa]); //得保证父亲的relation是正确的才能更新自己的
    x.relation = (x.relation + animal[fa].relation) % 3;
    return x.fa;
}

void Merge(int x, int y, int fx, int fy, int d) //将集合y合并到集合x
{
    animal[fy].fa = fx;
    animal[fy].relation = (animal[x].relation + d - 1 + 3 - animal[y].relation) % 3;
}

int main()
{
    n = read(); k = read();
    init();
    for(int i = 0; i < k; ++ i )
    {
        int d, x, y;
        d = read(); x = read(); y = read();
        if(x <= 0 || x > n || y <= 0 || y > n)
            ++ ans;
        else
        {
            int fx = find(animal[x]);
            int fy = find(animal[y]);
            if(fx != fy)
                Merge(x, y, fx, fy, d);
            else if(d == 1) //在同一个集合中,判断是不是同类
            {
                if(animal[x].relation != animal[y].relation)
                    ++ans;
            }
            else if(d == 2) //在同一个集合中,判断是不是x吃y,也就是y关于x的relation是不是1
            {
                if((3 - animal[x].relation + animal[y].relation) % 3 != 1)
                    ++ans;
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}


种类并查集

元素分为不同种类的并查集,可以转化为带权并查集解决,就像上面的解法~

but!!

种类并查集也有自己的解法:如果有 n n n个种类,在普通并查集的基础上,将原并查集扩展 n n n 倍。一共有 n n n 个域。


比如该题,我们一共有三个种类,也就有三个域。关系如下图:
在这里插入图片描述

如何表示这三个域?

A : [ 1 , n ] A:[1,n] A[1,n]
B : [ n + 1 , 2 n ] B:[n+1,2n] B[n+1,2n]
C : [ 2 n + 1 , 3 n ] C:[2n+1,3n] C[2n+1,3n]

一只动物x,在三个域中的表示: A : x ;   B : x + n ;   C : x + 2 n A:x;\ B:x+n;\ C:x+2n A:x; B:x+n; C:x+2n,三者之间也分别有着所在集合间的关系。
也就是 x x x x + n x+n x+n x + n x+n x+n x + 2 n x+2n x+2n x + 2 n x+2n x+2n n n n
在这里插入图片描述
所以,如果 1   x   y 1 \ x \ y 1 x y 即判断 x 、 y x、y xy 是不是同类,那么我们首先需要判断:

  1. 是不是有 x x x y y y,即Same(x+n, y) ?
  2. 是不是有 y y y x x x,即Same(y+n, x) ?
  • 如果都没有,说明 x 、 y x、y xy 是同类,那么合并 ( x , y )   ( x + n , y + n )   ( x + 2 n , y + 2 n ) (x,y) \ (x+n,y+n) \ (x+2n,y+2n) (x,y) (x+n,y+n) (x+2n,y+2n)

如果 2   x   y 2 \ x \ y 2 x y 即判断 x x x 是不是吃 y y y,那么我们首先需要判断:

  1. 是不是有 y y y x x x,即Same(y+n, x) ?
  2. 是不是有l x 、 y x、y xy 是同类,即Same(x,y) ?
  • 如果都没有,说明 x x x y y y,那么合并 ( x + n , y )   ( x + 2 n , y + n )   ( x , y + 2 n ) (x+n,y) \ (x+2n,y+n) \ (x,y+2n) (x+n,y) (x+2n,y+n) (x,y+2n)

最先写的种类并查集博客

写完写完写完!撒花花~~


#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;

int read()
{
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') { if (c == '-') f = -f; c = getchar(); }
    while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

const int maxN = 50004;

int n, k, ans;
int root[maxN * 3];
int Find(int x) { return root[x] == x ? x : root[x] = Find(root[x]); }
bool Same(int x, int y) { return Find(x) == Find(y); }
void Merge(int x, int y) { x = Find(x); y = Find(y); if(x != y) root[y] = x; }

void init()
{
    for(int i = 0; i <= n*3; ++ i )
        root[i] = i;
}

int main()
{
    n = read(); k = read();
    init();
    for(int i = 0; i < k; ++ i )
    {
        int d, x, y; d = read(); x = read(); y = read();
        if(x <= 0 || x > n || y <= 0 || y > n)
            ++ans;
        else
        {
            if(d == 1) //判断xy是否同类
            {
                if(Same(x + n, y) || Same(x + 2 * n, y)) //x吃y //y吃x
                    ++ ans;
                else
                {
                    Merge(x, y);
                    Merge(x + n, y + n);
                    Merge(x + 2 * n, y + 2 * n);
                }
            }
            else if(d == 2) //判断x是否吃y
            {
                if(Same(x, y) || Same(x + 2 * n, y)) //xy同类 //y吃x
                    ++ ans;
                else
                {
                    Merge(x + n, y);
                    Merge(x + 2 * n, y + n);
                    Merge(x, y + 2 * n);
                }
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}

本篇博客完结啦啦啦~撒花撒花撒花 😃

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值