1. UVa 11104 Cousin题目解析
这道来自UVa在线评测系统的题目编号为11104,标题为"Cousin"。作为算法竞赛中的经典题型,它属于树结构相关问题的范畴。题目通常会给定一棵家族树,要求我们判断两个节点是否属于"cousin"关系。
在家族树结构中,"cousin"关系指的是两个节点拥有相同的祖父节点(即父节点的父节点),但父节点不同。这与我们日常生活中对"表兄弟"的理解是一致的——你们的父母是兄弟姐妹关系,但你们自己不是直系亲属。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 树结构的基础表示方法
要解决这个问题,首先需要选择合适的数据结构来表示家族树。在算法竞赛中,常见的树表示方法有以下几种:
2.1 父指针表示法
这是最直观的表示方法,每个节点存储其父节点的信息。对于n个节点的树,我们可以用一个大小为n的数组parent[]来表示,其中parent[i]表示节点i的父节点。
cpp复制int parent[MAX_NODES]; // 假设MAX_NODES是足够大的常数
这种表示法的优点是简单直接,特别适合需要频繁查询父节点的场景。缺点是如果要查询子节点,需要遍历整个数组。
2.2 邻接表表示法
另一种常见的方法是使用邻接表,即每个节点维护一个包含其所有子节点的列表。在C++中可以用vector来实现:
cpp复制vector<int> children[MAX_NODES];
这种表示法在需要遍历子节点时非常高效,但查询父节点则需要额外的处理。
3. 解决Cousin问题的算法设计
3.1 基本思路
要判断两个节点是否为cousin关系,我们需要:
- 确认它们位于树的同一层级(即深度相同)
- 确认它们的父节点不同
- 确认它们的祖父节点相同
3.2 深度计算
首先需要计算每个节点的深度。可以采用递归或迭代的方式:
cpp复制int depth[MAX_NODES];
// 递归计算深度
void computeDepth(int node) {
if (node == root) {
depth[node] = 0;
return;
}
if (depth[parent[node]] == -1) {
computeDepth(parent[node]);
}
depth[node] = depth[parent[node]] + 1;
}
3.3 父节点和祖父节点检查
有了深度信息后,检查cousin关系的核心逻辑如下:
cpp复制bool isCousin(int a, int b) {
// 深度不同肯定不是cousin
if (depth[a] != depth[b]) return false;
// 父节点相同也不是cousin(是兄弟)
if (parent[a] == parent[b]) return false;
// 祖父节点必须相同
return parent[parent[a]] == parent[parent[b]];
}
4. 输入处理与边界情况
4.1 输入格式解析
UVa题目通常有特定的输入格式。对于这类家族树问题,输入可能包含:
- 节点数量n
- 每个节点的父节点信息
- 需要查询的节点对
4.2 边界情况处理
在实际编码中需要考虑以下特殊情况:
- 根节点的处理(通常parent[root] = -1或root自身)
- 查询节点中可能包含根节点
- 节点深度不足2层时不可能有cousin关系
- 相同节点的查询(根据题意可能不算cousin)
5. 完整解决方案示例
以下是使用C++实现的完整解决方案框架:
cpp复制#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int MAX_N = 1000;
int parent[MAX_N];
int depth[MAX_N];
void computeDepths(int n) {
memset(depth, -1, sizeof(depth));
for (int i = 0; i < n; i++) {
if (depth[i] == -1) {
int d = 0;
int current = i;
while (parent[current] != current && depth[current] == -1) {
current = parent[current];
d++;
}
depth[i] = (current == parent[current]) ? d : depth[current] + d;
}
}
}
bool isCousin(int a, int b, int n) {
if (a >= n || b >= n) return false;
if (depth[a] != depth[b]) return false;
if (parent[a] == parent[b]) return false;
if (parent[a] >= n || parent[b] >= n) return false;
return parent[parent[a]] == parent[parent[b]];
}
int main() {
int n, queries;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> parent[i];
}
computeDepths(n);
cin >> queries;
while (queries--) {
int a, b;
cin >> a >> b;
cout << (isCousin(a, b, n) ? "YES" : "NO") << endl;
}
return 0;
}
6. 算法优化与变种问题
6.1 预处理优化
对于静态树结构(建树后不再修改),可以预处理所有节点的深度和祖父节点信息,将每次查询的时间复杂度降至O(1)。
6.2 变种问题
类似的家族关系问题还有:
- 判断是否是兄弟节点(同一父节点)
- 判断是否是叔叔/阿姨节点(父节点的兄弟节点)
- 计算两个节点的最近公共祖先(LCA)
- 判断是否是后代节点
这些问题都可以用类似的树遍历方法解决。
7. 实际应用与扩展
虽然这个问题看起来是处理家族关系,但类似的树结构关系判断在计算机科学中有广泛应用:
- 文件系统中目录关系的判断
- HTML/XML文档树中元素关系的分析
- 组织结构图中人员关系的查询
- 生物信息学中的进化树分析
理解这类问题的解法,可以帮助我们更好地处理各种层次化数据结构的关系判断问题。
