• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • YouClaw
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【种类并查集】洛谷 P2024 [NOI2001] 食物链

题目

https://www.luogu.com.cn/problem/P2024

题解

种类并查集又称为扩展域并查集,是并查集的一种扩展形式,主要用于处理多组对立关系或多种状态划分的问题。在处理种类并查集问题时,有多少种类,就得开几倍的空间,用于表示所有的种类。

由题意,不妨假设现在有 \(n\) 种动物,且这 \(n\) 种动物必定都是 \(A,B,C\) 其中一种。它们的关系是 \(A\) 吃 \(B\),\(B\) 吃 \(C\),\(C\) 吃 \(A\)。那么对于该题,就是有三种种类,分别是同类、猎物和天敌。因此,使用种类并查集就需要开 3 倍空间大小。对于第 \(x\) 个人,不妨设其同类的集合为 \(exdsu[x]\),猎物的集合为 \(exdsu[x+n]\),天敌的集合为 \(exdsu[x+2 \times n]\)。可以画出其关系图为:
食物链1

不妨假设存在两只动物 \(a(1 \leq a \leq n)\) 和 \(b(1 \leq b \leq n)\),那么 \(a\) 的同类、猎物和天敌的集合分别为 \(exdsu[a],exdsu[a+n] 和 exdsu[a+2 \times n]\),\(b\) 的同类、猎物和天敌的集合分别为 \(exdsu[b],exdsu[b+n] 和 exdsu[b+2 \times n]\)。可以画出它们的关系图为:
食物链2

若 \(b\) 是 \(a\) 的猎物,则 \(exdsu[a+n]\) 和 \(exdsu[b]\) 是同类,那么有关系图(为方便画图,简单将 \(exdsu[x]\) 表示为 \(x\)):
食物链3

因为猎物关系和天敌关系不好表示,因此可以将它们转化为同类关系,使用种类并查集将所有的同类关系的集合合并起来。那么根据上述图表关系,有:

  • 若 \(x\) 和 \(y\) 的同类,则需要分别将 \(exdsu[x]与exdsu[y],exdsu[x+n]与exdsu[y+n]和exdsu[x+2 \times n] 与exdsu[y+ 2 \times n]\) 进行合并;
  • 若 \(y\) 是 \(x\) 的猎物,则需要分别将 \(exdsu[x]与exdsu[y+2 \times n],exdsu[x+n]与exdsu[y]和exdsu[x+2 \times n] 与exdsu[y+n]\) 进行合并。

参考代码

#include<bits/stdc++.h>
using namespace std;

constexpr int N = 5e4 + 1;
int n, k, x, y, ans;
char op;
int exdsu[N * 3];

int find(int x) {
    return x == exdsu[x] ? x : exdsu[x] = find(exdsu[x]);
}

void merge(int x, int y) {
    int fx = find(x), fy = find(y);
    exdsu[fy] = fx;
}

bool isLie() {
    if (x > n || y > n) return true;// x 或 y 比 n 大必是假话
    if (op == '2' && x == y) return true;// 吃自己是假话
    if (op == '1' && (find(x) == find(y + n) || find(x + n) == find(y))) return true;// 已经是异类却说是同类,假话
    if (op == '2' && (find(x) == find(y) || find(y + n) == find(x))) return true;// x 是 y 的同类或猎物
    return false;// 说的是真话
}

int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    cin >> n >> k;
    iota(exdsu + 1, exdsu + 1 + 3 * n, 1);
    while (k --) {
        cin >> op >> x >> y;
        if (isLie()) {
            ++ ans;
            continue;
        }
        if (op == '1') {
            merge(x, y);
            merge(x + n, y + n);
            merge(x + 2 * n, y + 2 * n);
        } else {
            merge(x + n, y);
            merge(x + 2 * n, y + n);
            merge(x, y + 2 * n);
        }
    }
    cout << ans << '\n';
    return 0;
}

posted on 2026-04-20 00:43  RomanLin  阅读(6)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3