https://blog.csdn.net/u014800748/article/details/43975097
https://www.luogu.com.cn/blog/ljcljc/solution-uva12219
【分析】
题目的意思是要求化简子树,使每一个子树都唯一存在,然而我们可以用一个结构体Node(x,l,r)Node(x,l,r)代表整个子树,xx代表当前子树根的字符串,ll代表当前子树的左子树,rr代表当前子树的右子树。每次我们可以用mapmap去实现O(logn)O(logn)的查询,建树的时候直接从左到右扫描字符串,如果当前字符是'((',则递归,继续建树;否则则表示当前的子树已经构建完成,给每一个子树一个旧编号,用一个固定数组存下来,遍历的时候用题目要求的新编号取代旧编号递归输出即可。
【丑陋的AC代码】
#include <iostream>
#include <cstdio>
#include <map>
using namespace std;
const int MAXN = 60010;
int T, Kase, cnt, done[MAXN];
char c[MAXN*5], *p;
struct Node
{
string s;
int x, l, r;
bool operator < (const Node&b)const//由于下面要用到map::count函数,所以必须要重载小于号
{
if (x != b.x)
return x < b.x;
if (l != b.l)
return l < b.l;//哈希相同则比较出现的顺序
return r < b.r;
}
}node[MAXN];
map<Node, int> ID;//记录子树的编号
int solve()
{
int id = cnt++;//从0开始编号
Node&u = node[id];
u.l = u.r = -1;
u.s = "";
u.x = 0;
while (isalpha(*p))
{
u.x = u.x * 27 + *p - 'a' + 1;//可以理解为27进制,a到z的编号为1到26
u.s.push_back(*p);//将该子树的字母放入s
p++;//向后扫描,遇到括号停止
}
if (*p == '(')//(L,R),递归处理左右子树
{
p++;//先跳过'('
u.l = solve(),p++;//返回左子树编号,并跳过','
u.r = solve(),p++;//返回右子树编号,并跳过')'
}
if (ID.count(u))
{
cnt--;//子树出现过,个数减少1
return ID[u];//返回这颗子树的编号
}
return ID[u] = id;//如果这棵树是首次出现,给它编号
}
void print(int v)
{
if (done[v] == Kase)
printf("%d", v + 1);//已经输出过了,输出序号即可
else
{
done[v] = Kase;//不需要对done数组初始化,只需要用这一轮特有的Kase标记即可
printf("%s", node[v].s.c_str());//输出树根的字母
if (node[v].l != -1)//含有左右子树
{
putchar('(');
print(node[v].l);//递归输出左右子树
putchar(',');
print(node[v].r);
putchar(')');
}
}
}
int main()
{
scanf("%d", &T);
for(Kase = 1; Kase <= T; Kase ++)
{
ID.clear();
cnt = 0;
scanf("%s", c);
p = c;//用指针p扫描c
print(solve());
printf("\n");
}
return 0;
}