1. 题目解析与解题思路
1.1 问题重述
我们面对的是一个典型的树结构问题:给定一棵包含n个节点的树,每个节点被标记为黑色或白色。需要找出这棵树上颜色不同的两个节点之间的最大距离。
这里的"距离"指的是树中两个节点之间的最短路径长度。在树结构中,任意两点之间有且仅有一条路径,因此距离可以明确计算。
1.2 树结构特性分析
树是一种特殊的无向连通无环图,具有以下重要性质:
- 任意两个节点之间有且仅有一条路径
- 边数 = 节点数 - 1
- 删除任意一条边都会使图不再连通
- 添加任意一条边都会形成环
这些性质使得树结构上的距离计算比一般图结构简单得多。对于最远点对问题,我们可以利用树的直径相关算法来优化解决方案。
1.3 解题思路概述
解决这个问题可以分几个步骤:
- 构建树的数据结构表示
- 预处理节点颜色信息
- 寻找所有可能的不同颜色节点对
- 计算这些节点对之间的距离
- 找出其中的最大值
直接暴力计算所有不同颜色节点对的距离虽然可行,但时间复杂度为O(n²),对于大规模数据效率不高。我们需要寻找更优化的算法。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法设计与优化
2.1 暴力解法分析
最直观的解法是枚举所有不同颜色的节点对,计算它们之间的距离,然后取最大值:
cpp复制int maxDistance = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (color[i] != color[j]) {
int dist = calculateDistance(i, j);
maxDistance = max(maxDistance, dist);
}
}
}
calculateDistance函数可以通过BFS或DFS实现,时间复杂度为O(n)。因此整体时间复杂度为O(n³),对于n较大的情况效率极低。
2.2 基于树直径的优化思路
我们可以利用树的直径算法来优化。树的直径是指树中最长路径的长度。经典算法是:
- 任选一个节点作为起点,找到距离它最远的节点u
- 以u为起点,找到距离它最远的节点v
- u和v之间的路径就是树的直径
对于本问题,我们需要找到不同颜色的最远点对。可以这样优化:
- 找到所有白色节点到黑色节点的最远距离
- 找到所有黑色节点到白色节点的最远距离
- 取两者中的最大值
2.3 两次BFS算法实现
具体实现可以采用两次BFS:
cpp复制pair<int, int> findFurthest(int start, const vector<vector<int>>& tree, const vector<int>& color) {
vector<int> distance(tree.size(), -1);
queue<int> q;
q.push(start);
distance[start] = 0;
int furthestNode = start;
while (!q.empty()) {
int current = q.front();
q.pop();
for (int neighbor : tree[current]) {
if (distance[neighbor] == -1) {
distance[neighbor] = distance[current] + 1;
q.push(neighbor);
if (distance[neighbor] > distance[furthestNode]) {
furthestNode = neighbor;
}
}
}
}
// 现在找与start颜色不同且最远的节点
int maxDist = 0;
int resultNode = -1;
for (int i = 0; i < tree.size(); i++) {
if (color[i] != color[start] && distance[i] > maxDist) {
maxDist = distance[i];
resultNode = i;
}
}
return {resultNode, maxDist};
}
int solve() {
// 第一次BFS:任意选择一个起点
auto [node1, dist1] = findFurthest(0, tree, color);
if (node1 == -1) {
// 所有节点颜色相同
return -1;
}
// 第二次BFS:从node1出发
auto [node2, dist2] = findFurthest(node1, tree, color);
return dist2;
}
这个算法的时间复杂度为O(n),大大优于暴力解法。
3. 完整代码实现
3.1 数据结构定义
首先定义输入数据的结构和读取方式:
cpp复制#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> color(n);
for (int i = 0; i < n; i++) {
cin >> color[i];
}
vector<vector<int>> tree(n);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
u--; v--; // 转换为0-based
tree[u].push_back(v);
tree[v].push_back(u);
}
// 后续处理...
}
3.2 BFS实现细节
完整的最远点对查找函数:
cpp复制pair<int, int> findFurthestDifferentColor(int start, const vector<vector<int>>& tree, const vector<int>& color) {
vector<int> distance(tree.size(), -1);
queue<int> q;
q.push(start);
distance[start] = 0;
// 标准BFS
while (!q.empty()) {
int current = q.front();
q.pop();
for (int neighbor : tree[current]) {
if (distance[neighbor] == -1) {
distance[neighbor] = distance[current] + 1;
q.push(neighbor);
}
}
}
// 寻找不同颜色最远节点
int maxDist = -1;
int resultNode = -1;
for (int i = 0; i < tree.size(); i++) {
if (color[i] != color[start] && distance[i] > maxDist) {
maxDist = distance[i];
resultNode = i;
}
}
return {resultNode, maxDist};
}
3.3 主逻辑处理
主函数中的处理逻辑:
cpp复制int main() {
// 输入读取...
// 第一次查找
auto [node1, dist1] = findFurthestDifferentColor(0, tree, color);
if (node1 == -1) {
// 所有节点颜色相同
cout << -1 << endl;
return 0;
}
// 第二次查找
auto [node2, dist2] = findFurthestDifferentColor(node1, tree, color);
cout << dist2 << endl;
return 0;
}
4. 算法正确性证明与复杂度分析
4.1 正确性证明
我们的算法基于以下关键观察:
- 在树结构中,最远点对必然包含在直径的两个端点中
- 对于有颜色限制的最远点对,我们可以通过两次BFS确保找到全局最优解
第一次BFS找到与任意起点颜色不同且最远的节点,第二次BFS从这个节点出发找到与它颜色不同且最远的节点。这样找到的必然是最远的不同颜色节点对。
4.2 时间复杂度分析
- 每次BFS的时间复杂度为O(n),因为需要访问所有节点和边
- 查找不同颜色最远节点的过程也是O(n)
- 总共进行两次这样的操作
- 因此整体时间复杂度为O(n)
这比暴力解法的O(n³)有了极大的提升,能够处理大规模数据。
4.3 空间复杂度分析
- 存储树结构需要O(n)空间(邻接表)
- BFS需要的队列和距离数组也是O(n)
- 因此空间复杂度为O(n)
5. 边界情况与测试用例
5.1 特殊输入处理
需要考虑以下几种特殊情况:
- 所有节点颜色相同:应返回-1或特定标识
- 只有两个节点且颜色不同:距离为1
- 链式结构:验证最长路径计算正确
- 星型结构:验证中心节点处理正确
5.2 测试用例示例
用例1:基本测试
code复制3
1 0 1
1 2
2 3
解释:节点1(白)-节点2(黑)-节点3(白),最远不同色对是1-3,距离2
用例2:所有节点同色
code复制4
1 1 1 1
1 2
2 3
3 4
应输出:-1
用例3:星型结构
code复制5
1 0 0 1 0
1 2
1 3
1 4
1 5
最远不同色对可能是2-4或3-5等,距离都是2
6. 常见问题与调试技巧
6.1 常见错误
- 节点编号处理:题目通常使用1-based编号,而代码中常用0-based,需要转换
- 颜色比较错误:确保比较的是节点的颜色值而非节点编号
- 距离初始化:BFS中距离数组初始值应为-1或INF,表示未访问
- 树构建错误:确保无向边被正确添加到邻接表中(双向添加)
6.2 调试建议
- 打印中间结果:在BFS后输出距离数组,验证计算是否正确
- 小规模测试:先用简单的手工计算验证的用例测试
- 边界测试:专门测试最小规模(n=1,2)和全同色情况
- 可视化:对于复杂结构,可以画出示意图辅助理解
6.3 性能优化
虽然我们的算法已经是O(n),但对于极大n还可以考虑:
- 使用更快的输入方法(如scanf代替cin)
- 预分配所有内存,避免动态分配开销
- 使用迭代而非递归实现DFS,避免栈溢出
7. 算法扩展与变种
7.1 多颜色最远点对
如果节点有超过两种颜色,寻找特定颜色组合的最远点对,可以:
- 对每种颜色组合分别应用本算法
- 使用更复杂的多源BFS优化
7.2 动态树结构
如果树结构会动态变化(添加/删除边),需要:
- 使用Link-Cut Tree等高级数据结构
- 维护直径信息
7.3 加权树
如果边有权重,距离是路径权重和:
- 需要修改BFS为Dijkstra算法
- 同样可以应用两次遍历的思想
在实际比赛中,理解问题本质并选择合适算法是关键。这道题考察了对树结构的理解和BFS的应用能力。通过分析问题特性,我们能够将看似复杂的O(n³)问题优化到O(n)的优雅解法。
