1. 问题背景与需求分析
农夫约翰遇到了一个有趣的奶牛交流问题。他拥有N头奶牛(2≤N≤10,000),这些奶牛之间需要通过M种不同的牛语(1≤M≤30,000)进行交流。每头奶牛掌握若干种语言,但只有当两头奶牛至少有一门共同语言时,它们才能直接交流。否则就需要其他奶牛作为"翻译"来间接交流。
问题的核心在于:农夫希望通过购买最少数量的语言课本(每本课本教授一门新语言),使得所有奶牛都能直接或间接地相互交流。换句话说,我们需要构建一个奶牛之间的连通网络,使得任意两头奶牛都能通过某种方式(直接或间接)进行沟通。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法选择与思路解析
2.1 为什么选择并查集
这个问题本质上是一个连通性问题,非常适合使用并查集(Disjoint Set Union,DSU)数据结构来解决。并查集具有以下优势:
- 高效的合并与查询:并查集的合并(union)和查找(find)操作在路径压缩和按秩合并优化下接近常数时间复杂度
- 天然的连通性表示:并查集能自然地表示元素之间的连通关系
- 动态处理能力:可以边输入数据边处理,不需要存储全部输入后再处理
2.2 核心解决思路
基本思路是将语言作为媒介,将掌握相同语言的奶牛连通起来:
- 初始化:假设最坏情况下,每头奶牛都需要学习一门新语言才能连通,初始需要N-1本书
- 语言追踪:维护一个数组f[M],记录每种语言第一个掌握的奶牛
- 连通处理:
- 如果当前语言未被记录,则记录当前奶牛
- 如果已有奶牛掌握该语言,则将当前奶牛与该奶牛连通
- 结果计算:每次成功连通一对奶牛,所需课本数减1
3. 详细实现解析
3.1 数据结构设计
cpp复制int prt[100001]; // 并查集父节点数组
int f[100001]; // 记录语言对应的第一个奶牛
int ans; // 需要购买的课本数
这里prt数组大小设为100001是为了容纳题目给定的最大N值(10,000),实际上可以更精确地设置为N+1。
3.2 关键函数实现
3.2.1 查找父节点(路径压缩优化)
cpp复制int getfather(int u) {
if(prt[u] == u)
return u;
return prt[u] = getfather(prt[u]); // 路径压缩
}
路径压缩通过在查找过程中将节点直接连接到根节点,显著提高了后续查询的效率。
3.2.2 合并操作
cpp复制void merge(int x, int y) {
int fx = getfather(x);
int fy = getfather(y);
if(fx == fy) return; // 已在同一集合
prt[fx] = fy;
ans--; //
