并查集与向量

一些真理

所以并查集到底是啥,是一个树?是一个连通分量?其实我们可以认为这是一个向量。为什呢?我们来看这个图。

请添加图片描述

假设存在关系A-B、B-C,根据这两个关系去构建并查集得到的结论不就是A-C有关系吗?实际上就是两个向量做加法,并查集的路径压缩就是在做这件事,向量路径缩短了,查询的速度自然而然就快起来了。而之前纠结的连通性问题其实就是向量存不存在的问题,换个角度来看,别有一番风味。结合一下实际,看一下一个经典案例。

http://poj.org/problem?id=1182

食物链问题,大意是某处存在三类生物,A能吃B,B能吃C,C能吃A,题目输入食物链的相关内容,要求检查输入食物链是否合理,这里单纯用一个数组描述两个节点rootX和X的关系已经不够,两个生物间存在同类,捕食和被捕食三种关系,这里用一个结构体来表示节点信息。

typedef struct Node { //并查集中节点的描述
    int preNode; //当前节点的上一级
    int relation; //节点和上一级节点的关系--0代表和上级是同类,1代表被上级吃,2代表吃上一级
} LineNode;
LineNode faction[50005]; //标记不同生物的关系

现在假设存在这样的关系,rootX吃X,rootY吃Y,不难看出这里二者构成了两个连通分量,如果此处指定rootX,rootY不相同,且X吃Y或者X和Y是同类,那么因为X和Y存在关系,那么这里要合并两个连通分量。

请添加图片描述

那么最后的结果应该是faction[AneY].preNode = AneX;但是只更新上下级是远远不够的,还应该更新关系域,关系域就涉及到向量的关系了,

请添加图片描述

在代码上写出来就是.

faction[AneY].relation = (faction[X].relation+(opt-1)-faction[Y].relation)%3; 
//opt-1代表的是x->y的关系,对于方向反了的向量,这里取负数即可表示

更多内容可以参考这边文章,讲的很好,直接讲透了。

https://blog.csdn.net/niushuai666/article/details/6981689

下面是我的代码。

/**
 * @file FoodLine.cpp
 * @author mingke (1510181330@qq.com.com)
 * @brief  http://poj.org/problem?id=1182
 * @version 0.1
 * @date 2022-04-29
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include<cstdio>

typedef struct Node { //并查集中节点的描述
    int preNode; //当前节点的上一级
    int relation; //节点和上一级节点的关系
} LineNode;
LineNode faction[50005]; //标记不同生物的关系

int findAncestor(int x);
using namespace std;
int main(void) {
    int n, k;
    scanf("%d %d", &n, &k);
    int sum = 0; //假话的数量
    int level = 1; //模拟的食物链层级
    //初始化数据
    for(int i = 1; i <= n; i++) {
        //默认情况下,生物自成一派,和自己属于同类关系
        faction[i].preNode = i; 
        faction[i].relation = 0;
    }
    while(k--) {
        int opt, num1, num2;
        scanf("%d %d %d", &opt, &num1, &num2);
        if(num1 > n || num2 > n) {
            sum++;
            continue;
        }
        int AneX = findAncestor(num1); //第一个生物的祖先
        int AneY = findAncestor(num2); //第二个生物的祖先
        if(AneX != AneY) { //祖先不同,向量合并
            faction[AneY].preNode = AneX;
            faction[AneY].relation = (faction[num1].relation+(opt-1)-faction[num2].relation+3)%3; //加三防治减法弄出来负数
        }else { //祖先相同
            if(opt==1 && faction[num1].relation!=faction[num2].relation) { //只有当二者的关系域相等的时候才可能是同类
                sum++; //假话
            }
            if(opt==2 && (faction[num2].relation-faction[num1].relation+3)%3!=1) { //向量模拟二者关系,比较是否成立 
                sum++; //假话
            }
        }
    }
    printf("%d\n", sum);
    return 0;
}
//查找指定节点的双亲节点
int findAncestor(int x) {
    //对于这里的路径压缩而言,要将节点X从其父节点挂载到其父节点的父节点,并更新关系域
    if(faction[x].preNode == x) {
        return x;
    }
    //准备更新节点
    int pre = faction[x].preNode;
    faction[x].preNode = findAncestor(pre); //更新父节点
    faction[x].relation = (faction[pre].relation+faction[x].relation)%3; //更新关系
    return faction[x].preNode;
}

// int faction[50005]; //标记不同生物的种类
// int fixLevel(int num);
// using namespace std;
// int main(void) {
//     int n, k;
//     scanf("%d %d", &n, &k);
//     int sum = 0; //假话的数量
//     int level = 1; //模拟的食物链层级
//     //初始化数据
//     for(int i = 1; i <= n; i++) {
//         faction[i] = 0;
//     }
//     while(k--) {
//         int opt, num1, num2;
//         scanf("%d %d %d", &opt, &num1, &num2);
//         if(num1 > n || num2 > n) {
//             sum++;
//             continue;
//         }
//         if(opt == 1) { //是同类
//             if(faction[num1]==0 || faction[num2]==0) { //有一/两种生物第一次出现
//                 if(faction[num1]!=0) { //第一种生物出现过,以他为准
//                     faction[num2] = faction[num1];
//                 }else if(faction[num2]!=0) { //第二种生物出现过,以他为准
//                     faction[num1] = faction[num2];
//                 }else { //均未出现过,按照默认等级
//                     faction[num1] = faction[num2] = level%3;
//                 }
//             }else {
//                 //两种生物均有自己的食物链层级,判断层级是否相同
//                 if(faction[num1] != faction[num2]) {
//                     sum++;
//                 }
//             }
//         }

//         if(opt == 2) { //捕食关系
//             if(num1 == num2) {
//                 sum++;
//                 continue;
//             }
//             if(faction[num1]==0 || faction[num2]==0) {
//                 if(faction[num1]!=0) { //如果捕食者的层级已知
//                     faction[num2] = fixLevel(faction[num1]-1);
//                 }else if(faction[num2]!=0) { //如果被捕食者的层级已知
//                     faction[num1] = fixLevel(faction[num2]+1);
//                 }else {
//                     //二者的层级均未确定
//                     faction[num2] = fixLevel(level%3);
//                     level++;
//                     faction[num1] = fixLevel(level%3);
//                 }
//             }else {
//                 //二者食物链等级均存在,则比较二者的食物链等级
//                 if(faction[num1]-faction[num2]==1 || (faction[num1]==1 && faction[num2==3])) {
//                     //  正常的捕猎关系
//                 }else {
//                     sum++; //假话
//                 }
//             }
//         }
//     }
//     printf("%d\n", sum);
//     return 0;
// }
// //确定生物食物链层级
// int fixLevel(int num) {
//     if(num == 0) {
//         return 3;
//     }else if(num == 4) {
//         return 1;
//     }else {
//         return num;
//     }
// }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值