记录143
#include<bits/stdc++.h>
using namespace std; // 使用标准命名空间std
string s; // 存储01字符串
// 递归函数:处理字符串区间 [l, r]
void dfs(int l,int r) {
// 1. 判断当前区间的类型
bool has0=false,has1=false;
for(int i=l;i<=r;i++) {
if(s[i]=='0') has0=true;
else has1=true;
}
char type; // 当前节点的类型
if(has0&&has1) type='F'; // 既有0又有1
else if(has0) type='B'; // 只有0
else type='I'; // 只有1
// 2. 如果区间长度大于1,继续递归切分左右子树
if(l<r) {
int mid=(l+r)/2;
dfs(l,mid); // 递归左子树
dfs(mid+1,r); // 递归右子树
}
// 3. 后序遍历:最后输出当前节点的类型
cout<<type;
}
int main() { // 主函数入口
ios::sync_with_stdio(false); // 关闭同步,加速IO
cin.tie(0);
int n;
cin>>n>>s; // 读入N和01串
// 启动递归,初始区间是整个字符串 [0, s.size()-1]
dfs(0,s.size()-1);
cout<<"\n"; // 输出换行
return 0;
}
题目传送门
https://www.luogu.com.cn/problem/P1087
前言
我是一名专注信奥赛(CSP-J/S、NOIP)的教练。
- 如果你觉得这篇题解对你有帮助,欢迎点击关注我的优快云账号,我会持续更新高质量算法解析。
- 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
- 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。
核心解题思路
这道题是一道非常经典的递归分治与二叉树遍历问题。
-
核心原理:
题目给出了 FBI 树的递归构造方法:根节点的类型由当前字符串决定,如果字符串长度大于 1,则从中间一分为二,分别构造左子树和右子树。这天然契合二叉树的结构。题目要求输出后序遍历序列,即“左子树 -> 右子树 -> 根节点”的顺序。 -
算法设计(分治递归):
我们不需要真正地在内存中构建出树的结构(即不需要定义节点类或结构体)。只需要写一个递归函数,传入当前处理的字符串区间[l, r]。在函数内部,先判断当前区间对应的节点类型,然后如果区间长度大于 1,就递归处理左半区间和右半区间,最后输出当前节点的类型。这种“先递归左右,再输出自身”的操作,恰好就是后序遍历。
代码分块详细解释
1. 头文件与全局变量定义
#include<bits/stdc++.h>
using namespace std; // 使用标准命名空间std
string s; // 存储01字符串
- 详细分析:题目中字符串的长度为 2N2N ,当 N=10N=10 时,字符串长度最多为 210=1024210=1024 。使用
string类型存储非常方便,且可以像数组一样通过下标访问。将字符串定义为全局变量,可以避免在递归函数中反复传递字符串,提高运行效率。
2. 核心逻辑:分治递归函数(dfs)
// 递归函数:处理字符串区间 [l, r]
void dfs(int l, int r) {
// 1. 判断当前区间的类型
bool has0=false, has1=false;
for(int i=l; i<=r; i++) {
if(s[i]=='0') has0=true;
else has1=true;
}
char type; // 当前节点的类型
if(has0 && has1) type='F'; // 既有0又有1
else if(has0) type='B'; // 只有0
else type='I'; // 只有1
// 2. 如果区间长度大于1,继续递归切分左右子树
if(l < r) {
int mid = (l+r)/2;
dfs(l, mid); // 递归左子树
dfs(mid+1, r); // 递归右子树
}
// 3. 后序遍历:最后输出当前节点的类型
cout << type;
}
- 详细分析:这是本题的灵魂所在,完美体现了“分治”与“后序遍历”的结合。
- 节点类型判定:通过一个
for循环遍历当前区间[l, r]内的所有字符,用两个布尔变量has0和has1记录是否出现过 0 和 1。根据两者的组合情况,确定当前节点是 'F'、'B' 还是 'I'。 - 递归切分:如果
l < r,说明当前区间长度大于 1,需要继续向下构造子树。计算中间位置mid = (l+r)/2,然后分别对左半区间[l, mid]和右半区间[mid+1, r]发起递归调用。 - 后序输出:注意代码的执行顺序是先调用
dfs(l, mid)(处理左子树),再调用dfs(mid+1, r)(处理右子树),最后才执行cout << type(输出根节点)。这严格遵循了后序遍历“左右根”的规则,保证了最终拼接出的字符串就是正确的后序遍历序列。
- 节点类型判定:通过一个
3. 主函数:输入处理与启动递归
int main() {
ios::sync_with_stdio(false); // 关闭同步,加速IO
cin.tie(0);
int n;
cin >> n >> s; // 读入N和01串
// 启动递归,初始区间是整个字符串 [0, s.size()-1]
dfs(0, s.size()-1);
cout << "\n"; // 输出换行
return 0;
}
- 详细分析:主函数负责读取输入的 NN 和 01 串,并启动递归。
dfs(0, s.size()-1)表示最初我们要处理的是整个字符串,左右边界分别是0和字符串长度-1。
核心逻辑总结表
| 代码模块 | 核心变量/操作 | 精炼作用 | 解决的痛点 |
|---|---|---|---|
| 节点类型判定 | has0 与 has1 布尔标记 | 遍历当前区间,判断包含的字符种类 | 准确确定当前递归层对应的节点是 F、B 还是 I |
| 递归切分 | int mid = (l+r)/2 | 将当前区间从正中间一分为二 | 完美契合 FBI 树“等长左右子串”的构造规则 |
| 递归左子树 | dfs(l, mid) | 递归处理左半部分区间 | 深入树的左侧分支,遵循后序遍历“先左”的规则 |
| 递归右子树 | dfs(mid+1, r) | 递归处理右半部分区间 | 深入树的右侧分支,遵循后序遍历“后右”的规则 |
| 根节点输出 | cout << type | 在左右子树递归结束后输出当前类型 | 遵循后序遍历“最后根”的规则,直接拼接出最终答案 |
| 递归终止 | if(l < r) | 当区间长度为1时不再向下递归 | 保证了递归在叶子节点处正确停止,防止死循环 |
439



被折叠的 条评论
为什么被折叠?



