紫书刷题进行中,题解系列【GitHub|CSDN】
例题6-17 UVA10562 Undraw the Trees(29行AC代码)
题目大意
本题理解题目很关键
给定ascii字符表示的多叉树,输出指定的括号表达形式。
其中存在共用孩子结点情况和重复结点名情况,如下图所示
- 共用孩子结点:B的孩子是C和E,D的孩子也是C和E
- 重复结点名:第一个结点A和最后一个同名,但输出时照输不误
A
|
--
BD
||
--
CE
|
A
思路分析
最直接的思路是解析字符串,构造多叉树,先序遍历输出,从多叉树到输出转换很容易,但是解析字符串较为繁琐,因此问自己一个问题:一定要构建多叉树吗?
先看直接思路,若是解析字符串,必须通过|--
等字符间的位置关系来确定孩子和父亲关系,但转念一想,我能够解析这些关系,说明我已经能够得到字符之间的位置关系了,那直接把整棵树存在一个二维数组中,不就可以随时获取字符间位置关系了吗?
通过以上分析,发现可以省略实际建树的过程,在分析字符间关系时顺便输出结果,代码也极为简短
之后会给出两种思路对于的代码,可对比分析优劣
注意点
-
注意访问数组的元素时先判断是否越界,因为本题输入中每一行宽度不一定相同
-
scanf和fgets/getline
混用时,注意吸收多余换行 -
空树特判
AC代码(C++11)
二维数组递归输出(29行)
#include<bits/stdc++.h>
using namespace std;
int T;
vector<string> buf; // 存储输入的树
void dfs(int r, int c) { // 递归打印以buf(r,c)为根的子树
printf("%c", buf[r][c]);
if (r < buf.size()-1 && buf[r+1][c] == '|') { // 有孩子
putchar('('); int l=c;
while (buf[r+2][l] == '-') l--; // 查找--的左边界
for (int i=l+1; i < buf[r+2].size() && buf[r+2][i] == '-'; i ++) {
if (i < buf[r+3].size() && buf[r+3][i] != ' ') dfs(r+3, i); // 注意r+3的有效范围判断
}
putchar(')');
}
else printf("()");
}
int main() {
scanf("%d", &T); getchar(); // 吸收多余换行
string s;
while (T --) {
buf.clear();
while (getline(cin, s) && s != "#") buf.push_back(s); // 以数组存储树
putchar('(');
for (int i=0; !buf.empty() && i < buf[0].size(); i ++)
if (buf[0][i] != ' ') {dfs(0,i); break;} // 找到根
puts(")");
}
return 0;
}
字符串解析建树(51行)
#include<bits/stdc++.h>
using namespace std;
int T;
void dfs(map<int,vector<int> >&tree, vector<char>& dict, int root=0) { // 根据多叉树递归打印
if (dict.size() > root) printf("%c", dict[root]);
if (!tree[root].empty()) {
printf("(");
for (auto t : tree[root]) dfs(tree, dict, t);
printf(")");
}
else printf("()");
}
int main() {
scanf("%d", &T); getchar(); // 吸收多余换行
string s[4]; // 4行:根,|,-,孩子
while (T --) {
map<int,vector<int> > tree; // 多叉树
vector<char> dict; // 结点字符字典(给每个字符进行编号,处理重复字符)
map<int,int> id; // 每个位置->对应字符的id
getline(cin, s[0]);
if (s[0][0] == '#') {printf("()\n"); continue;} // 空树
auto p = find_if(s[0].begin(), s[0].end(), [](char& ch){return ch != ' ';});
id[p - s[0].begin()] = dict.size(); dict.push_back(*p);
while (getline(cin, s[1]) && s[1][0] != '#') {
getline(cin, s[2]); getline(cin, s[3]);
vector<int> a[3]; // a[1]表示|所在位置,a[2]表示每段-的起止位置
for (int i=0; i < s[1].size(); i ++) if (s[1][i] == '|') a[1].push_back(i); // |的位置
int flag=0;
for (int i=0; i < s[2].size(); i ++) { // -分割区间:[起始,结束)
if (flag == 0 && s[2][i] == '-') a[2].push_back(i), flag=1; // 起点
else if (flag == 1 && s[2][i] == ' ') a[2].push_back(i), flag=0; // 终点
}
if (a[2].size() % 2 != 0) a[2].push_back(s[2].size()); // 最后一段-
map<int,int> tid; // 临时存储
for (int i=0; i < s[3].size(); i ++) { // 孩子处理
if (s[3][i] != ' ') {
for (int j=0; j < a[2].size(); j += 2) // 查询在哪一段
if (a[2][j] <= i && i < a[2][j+1]) {
tid[i] = dict.size(); dict.push_back(s[3][i]);
for (int k=0; k < a[1].size(); k ++) { // 查询符合区间的父结点
if (a[2][j] <= a[1][k] && a[1][k] < a[2][j+1]) tree[id[a[1][k]]].push_back(dict.size()-1);
}
}
}
}
id = tid; s[0] = s[3]; // 更新
}
putchar('('); dfs(tree, dict, 0); puts(")");
}
return 0;
}