1. 题目背景与需求分析
这道题目来自APIO2007竞赛,编号P3621,题目名为"风铃"。这是一道典型的树形结构处理问题,考察选手对二叉树遍历和递归算法的掌握程度。
题目描述了一个特殊的风铃结构:由多层节点组成,每个节点最多有两个子节点(左和右)。我们需要通过编程判断这个风铃是否满足"所有叶子节点都在同一层"的条件,如果不满足,需要计算最少交换多少次左右子树能使风铃满足这个条件。
2. 数据结构设计与解析
2.1 二叉树表示方法
对于这类树形结构问题,我们通常采用结构体来表示节点:
cpp复制struct Node {
int left, right;
int depth; // 记录节点深度
bool is_leaf; // 标记是否为叶子节点
};
这种表示方法有几个关键点:
- 使用整数索引而非指针,避免内存管理问题
- 显式记录深度信息,便于后续判断
- 明确标记叶子节点,简化条件判断
2.2 输入处理技巧
题目输入格式为每个节点的左右子节点编号。处理时需要注意:
cpp复制vector<Node> tree(N+1); // 节点从1开始编号
for(int i=1; i<=N; ++i){
int l, r;
cin >> l >> r;
tree[i].left = l;
tree[i].right = r;
tree[i].is_leaf = (l == -1 && r == -1);
}
注意:题目中-1表示空节点,这在竞赛题中很常见,处理时要特别注意边界条件。
3. 核心算法实现
3.1 深度计算与叶子节点检查
首先需要通过DFS或BFS计算每个节点的深度:
cpp复制void calculate_depth(vector<Node>& tree, int node, int depth){
if(node == -1) return;
tree[node].depth = depth;
calculate_depth(tree, tree[node].left, depth+1);
calculate_depth(tree, tree[node].right, depth+1);
}
然后检查所有叶子节点是否在同一深度:
cpp复制bool check_uniform_depth(const vector<Node>& tree){
int leaf_depth = -1;
for(const auto& node : tree){
if(node.is_leaf){
if(leaf_depth == -1) leaf_depth = node.depth;
else if(node.depth != leaf_depth) return false;
}
}
return true;
}
3.2 交换操作的最小次数计算
当叶子节点不在同一层时,我们需要计算最小交换次数。这里的关键观察是:
- 如果一个节点的左右子树深度范围有重叠,则无论如何交换都无法满足条件
- 否则,交换次数取决于左右子树是否需要交换
实现代码:
cpp复制pair<int,int> solve(vector<Node>& tree, int node){
if(node == -1) return {0,0};
auto left = solve(tree, tree[node].left);
auto right = solve(tree, tree[node].right);
if(left.first == -1 || right.first == -1)
return {-1, -1};
if(left.second < right.first || right.second < left.first)
return {-1, -1};
int swaps = left.second > right.first ? 1 : 0;
return {min(left.first, right.first), max(left.second, right.second)};
}
4. 完整代码实现与优化
4.1 完整AC代码
结合上述分析,完整的解决方案如下:
cpp复制#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Node {
int left, right;
int min_depth, max_depth;
bool is_leaf;
};
pair<int,int> dfs(vector<Node>& tree, int node, bool& possible, int& swaps){
if(node == -1) return {0,0};
auto left = dfs(tree, tree[node].left, possible, swaps);
auto right = dfs(tree, tree[node].right, possible, swaps);
if(!possible) return {0,0};
if((left.second < right.first) || (right.second < left.first)){
possible = false;
return {0,0};
}
if(left.second > right.first){
swaps++;
swap(left, right);
}
return {min(left.first, right.first)+1, max(left.second, right.second)+1};
}
int main(){
int N;
cin >> N;
vector<Node> tree(N+1);
for(int i=1; i<=N; ++i){
cin >> tree[i].left >> tree[i].right;
tree[i].is_leaf = (tree[i].left == -1 && tree[i].right == -1);
}
bool possible = true;
int swaps = 0;
dfs(tree, 1, possible, swaps);
if(!possible) cout << -1 << endl;
else cout << swaps << endl;
return 0;
}
4.2 性能优化要点
- 记忆化搜索:虽然本题数据规模不大,但对于更大的树可以考虑记忆化计算结果
- 提前终止:一旦发现不可能满足条件,立即终止递归
- 输入优化:使用更快的输入方法(如scanf或快速IO)处理大规模数据
5. 常见错误与调试技巧
5.1 典型错误案例
- 深度计算错误:忘记在递归时+1导致深度计算错误
- 交换条件判断错误:错误地认为只要左右子树深度不同就需要交换
- 边界条件处理不当:对空节点(-1)的处理不完善
5.2 调试建议
- 小规模测试:先用手算能验证的小树测试
- 打印中间结果:在递归过程中打印当前节点的深度范围
- 可视化树结构:对于复杂案例,可以先将树结构画出来辅助理解
cpp复制// 调试打印函数示例
void print_tree(const vector<Node>& tree, int node, int indent=0){
if(node == -1) return;
cout << string(indent, ' ') << node << " (";
cout << tree[node].min_depth << "," << tree[node].max_depth << ")" << endl;
print_tree(tree, tree[node].left, indent+2);
print_tree(tree, tree[node].right, indent+2);
}
6. 算法复杂度分析
- 时间复杂度:O(N),每个节点只被访问一次
- 空间复杂度:O(N),存储树结构需要线性空间
- 递归深度:最坏情况下等于树的高度,对于平衡二叉树是O(logN)
在实际竞赛中,这种解法对于N=1e5量级的数据也能轻松通过。