跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

扩展域并查集原理及经典题目解析

介绍扩展域并查集的概念,用于处理元素间存在多种关系(如朋友/敌人、捕食/被捕食)的场景。通过将每个元素拆分为多个域来维护不同状态下的关系。文中通过“团伙”和“食物链”两个经典 OJ 题目,详细讲解了如何构建 2n 或 3n 大小的数组,以及合并集合的具体逻辑,并提供了完整的 C++ 代码实现。

AiEngineer发布于 2026/3/30更新于 2026/10/26113 浏览
扩展域并查集原理及经典题目解析

扩展域并查集

普通的并查集只能解决各元素之间仅存在一种相互关系。例如在普通并查集的《亲戚》问题中,a 和 b 是亲戚,b 和 c 是亲戚,则 a 和 c 也是亲戚。

但如果存在各元素之间存在多种相互关系,普通并查集就无法解决。比如:a 和 b 是敌人,b 和 c 是敌人,那么 a 和 c 的关系是什么呢?根据'敌人的敌人是朋友',a 和 c 其实是朋友关系。

解决这类有多种关系的问题需要对并查集进行扩展:将每个元素拆分成多个域,每个域代表一种状态或者关系。通过维护这些域之间的关系,来处理复杂的约束条件。

比如现在有 n 个人,它们之间可能是朋友关系也可能是敌人关系。我们可以把数组扩大成两倍,开辟一个大小为 2n 的数组,[1, n] 这个区间表示朋友域,[n+1, 2n] 区间表示敌人域。接下来:

  • 如果 x 和 y 是朋友,正常处理,把 x 和 y 合并成一个集合;
  • 如果 x 和 y 是敌人,那么我们把 x 和 y+n 合并成一个集合,表示 x 和 y 是敌人,同时也把 x+n 和 y 合并成一个集合。

这样就可以利用两个域,将两种关系维护起来。

实战案例

1. 团伙

【题目链接】

P1892 [BalticOI 2003] 团伙 - 洛谷

【题目描述】

现在有 n 个人,他们之间有两种关系:朋友和敌人。我们知道:一个人的朋友的朋友是朋友,一个人的敌人的敌人是朋友。

现在要对这些人进行组团。两个人在一个团体内当且仅当这两个人是朋友。请求出这些人中最多可能有的团体数。

【输入格式】

第一行输入一个整数 n 代表人数。

第二行输入一个整数 m 表示接下来要列出 m 个关系。

接下来 m 行,每行一个字符 opt 和两个整数 p, q,分别代表关系(朋友或敌人),有关系的两个人之中的第一个人和第二个人。其中 opt 有两种可能:如果 opt 为 F,则表明 p 和 q 是朋友。如果 opt 为 E,则表明 p 和 q 是敌人。

【输出格式】

一行一个整数代表最多的团体数。

【说明/提示】

对于 100% 的数据,2 ≤ n ≤ 1000,1 ≤ m ≤ 5000,1 ≤ p, q ≤ n。

解题思路

开辟一个 2n+10 大小的数组,[1, n] 区间表示朋友域,[n+1, 2n] 区间表示敌人域。如果 x 和 y 是朋友,那么把 x 和 y 合并成一个集合;如果 x 和 y 是敌人,那么我们把 x 和 y+n 合并成一个集合,x+n 和 y 合并成一个集合。

处理完之后我们只需看有多少个根节点即可。

代码实现
#include <iostream>
using namespace std;

const int N = 1010;
int pa[2 * N]; // 扩展域并查集,两种关系,扩展出两个域
 n, m;

{
     ( i = ; i <=  * n; i++) pa[i] = i;
}

{
     (pa[x] == x)  x;
     pa[x] = (pa[x]);
}



{
     fx = (x);
     fy = (y);
    pa[fx] = fy;
}

{
    cin >> n >> m;
    ();
     (m--) {
         opt;
        cin >> opt;
         p, q;
        cin >> p >> q;
         (opt == ) (p, q);
         {
            
            (p + n, q);
            (q + n, p);
        }
    }
     cnt = ;
     ( i = ; i <= n; i++) {
         (pa[i] == i) cnt++;
    }
    cout << cnt << endl;
     ;
}
int
void init()
for
int
1
2
int find(int x)
if
return
return
find
// 细节:由于最后我们看有多少个根节点的时候只遍历朋友域
// 因此我们合并的时候要让朋友域中的节点作为父亲节点合并
void uni(int x, int y)
int
find
int
find
int main()
init
while
char
int
if
'F'
uni
else
// 注意:让朋友域作为父亲合并
uni
uni
int
0
for
int
1
if
return
0

2. 食物链

【题目链接】

P2024 [NOI2001] 食物链 - 洛谷

【题目描述】

动物王国中有三类动物 A, B, C,这三类动物的食物链构成了有趣的环形。A 吃 B,B 吃 C,C 吃 A。

现有 N 个动物,以 1∼N 编号。每个动物都是 A, B, C 中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这 N 个动物所构成的食物链关系进行描述:第一种说法是 1 X Y,表示 X 和 Y 是同类。第二种说法是 2 X Y,表示 X 吃 Y。

此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。当前的话与前面的某些真的话冲突,就是假话;当前的话中 X 或 Y 比 N 大,就是假话;当前的话表示 X 吃 X,就是假话。

你的任务是根据给定的 N 和 K 句话,输出假话的总数。

【输入格式】

第一行两个整数,N, K,表示有 N 个动物,K 句话。

第二行开始每行一句话。格式见题目描述与样例。

【输出格式】

一行,一个整数,表示假话的总数。

【说明/提示】

对于全部数据,1 ≤ N ≤ 5 × 10^4,1 ≤ K ≤ 10^5。

解题思路

通过题目描述,我们可以得出三类动物之间一共有 3 种关系:捕食、被捕食、同类。

所以想要维护它们之间的关系可以采用扩展域并查集,扩展出来 3 个域,需要开辟一个 3n 的数组。针对某一个动物 x(1 ≤ x ≤ n),扩展出 3 个域:同类域 [1, n],捕食域 [n+1, 2n],被捕食域 [2n+1, 3n]。

如果 x 和 y 是同类,那么:

  • x 和 y 是同类,合并 x 和 y;
  • x 和 y 所捕食的物种是同类,合并 x+n 和 y+n;
  • 捕食 x 和 y 的物种是同类,合并 x+2n 和 y+2n。

如果 x 捕食 y,那么:

  • x 捕食 y,合并 x 和 y+n;
  • x 捕食的物种和捕食 y 的物种是一类,合并 x+n 和 y+2n;
  • 捕食 x 的物种和 y 捕食的物种是一类,合并 x+2n 和 y+n。
代码实现
#include <iostream>
using namespace std;

const int N = 5e4 + 10;
int pa[3 * N];
int n, k;

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

int find(int x) {
    if (pa[x] == x) return x;
    return pa[x] = find(pa[x]);
}

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

bool issame(int x, int y) {
    return find(x) == find(y);
}

int main() {
    scanf("%d%d", &n, &k);
    int cnt = 0; // 记录假话个数
    init();
    while (k--) {
        int r, x, y;
        scanf("%d%d%d", &r, &x, &y);
        // 如果有一个比 n 大或者 x 吃 x
        if ((x > n || y > n) || (r == 2 && x == y)) {
            cnt++;
            continue;
        }
        // x, y 是同类关系
        if (r == 1) {
            // 如果发现 x 如吃 y 或者 x 被 y 吃
            bool t = issame(x, y + n) || issame(x, y + 2 * n);
            if (t) // 那么就矛盾了,说明是假话
            {
                cnt++;
                continue;
            }
            // 否则就是真话
            uni(x, y);
            uni(x + n, y + n);
            uni(x + 2 * n, y + 2 * n);
        }
        // x 吃 y
        else {
            // 此时如果发现 x, y 是同类或者 x 被 y 吃
            bool t = issame(x, y) || issame(x, y + 2 * n);
            if (t) // 那么就矛盾了,说明是假话
            {
                cnt++;
                continue;
            }
            // 否则就是真话
            uni(y + n, x);
            uni(x + n, y + 2 * n);
            uni(x + 2 * n, y);
        }
    }
    cout << cnt << endl;
    return 0;
}

目录

  1. 扩展域并查集
  2. 实战案例
  3. 1. 团伙
  4. 解题思路
  5. 代码实现
  6. 2. 食物链
  7. 解题思路
  8. 代码实现

更多推荐文章

查看全部
  • Python 数据分析与自动化 30 个实战技巧
  • Springer 期刊 Signal, Image and Video Processing 投稿指南与常见问题
  • PCL 点云处理核心算法与实战汇总(C++ 版)
  • 无人机航测内业处理:iTwin Capture Modeler 建模流程
  • OpenClaw 本地部署与 AI Agent 开发指南
  • ALVR 无线 VR 串流项目快速上手指南
  • OpenClaw 框架深度指南:HEARTBEAT 机制与定时任务配置
  • Rock 5B+ 部署 MAVSDK 控制无人机及 WSL 运行 PX4 仿真环境搭建
  • VSCode Copilot MCP 扩展使用教程
  • 基于 OpenClaw 与 Claude 的自动化写作工作流搭建
  • 2025 0xGame Web 方向题目全解
  • Visual C++ 运行库详解与常见问题解决方案
  • Buzz 语音转文字离线工具安装与使用指南(含 Whisper 模型)
  • Ubuntu 22.04 下 libwebkit2gtk-4.1-0 依赖安装问题解析
  • 技术学习资源精选:电子书、软件与 AI 工具合集
  • Windows 7 系统运行最新 Python 版本的解决方案
  • 大模型技术原理与应用指南:从入门到精通
  • Git 高级用法实战指南:从协作到故障恢复
  • 基于大语言模型的本地知识库问答离线部署
  • Presenton 故障排除指南:常见问题与解决方案

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online