题目概述
输入样例
11 5
html
..head
....title
..body
....h1
....p #subtitle
....div #main
......h2
......p #one
......div
........p #two
p
#subtitle
h3
div p
div div p
输出样例
3 6 9 11
1 6
0
2 9 11
1 11
思路概述
CSP真题中一道T3的题目,也就是大模拟。题目也的确符合大模拟一贯的风格,背景比较复杂,情况较多,但是在时间性能上并没有很高的要求,因此耐心设计可以通过暴力得求解解决。
在叙述算法之前,先读懂题目的意思至关重要。题目的场景和要求都基于结构化文档的选择。结构化文档有三个关键元素:
1、层次
2、标签
3、属性
因为整个文档具有层次结构,所以要维持一定的树结构。每一个层次上一定有一个标签,但属性不一定都有,如果没有则为空。
要进行的操作则是通过输入的信息对文档的某一行或某几行进行选取:
1、输入一个标签,则找到所有匹配该标签的行
2、输入一个属性·,则找到匹配该属性的行
3、输入一个标签-属性序列,从序列的末端开始向上匹配。如果有一个序列A-B,则B如果匹配,且其层次上的祖先结构上有匹配的A,则选取B的一行。(这里要注意,如果能够匹配,要选取的是最下层匹配对应的行)
了解了要进行的操作我们可以将问题转换为两个方面:
模块1、输入结构化文档,并维持其树结构和信息的存储
因为要维持树结构,father、son之类的属性必须存在,方便找到某一个节点的父节点或者子节点,但是由于在序列匹配时选取的行数是最下层的对应匹配,也就是说我们在判断A-B-C的序列C是否被选取时,只需要依次判断C->B->A能否被依次匹配上。因此我们树结构中存放的是father,一旦子节点匹配,利用father找父节点,如果父节点依次也都匹配,将最初的子节点加入vector即可。
基于上面的分析,我们建立了如下所示的节点结构体:
struct node
{
string _tag;//标签
string _id;//属性id
int father;//父节点的行数
node()
{
_tag.clear();
_id.clear();
father = 0;
}
};
但是还有另外的问题需要考虑,在给定一个标签或属性后,怎样找到他在哪一行出现过?
考虑到使用时查的次数可能非常多,为了维护更高的时间性能,选择使用map来存放,每一个string对应一个vector,vector记录了该string出现的所有行数。
这里要注意的是:
因为标签部分对大小写并不敏感,因此为了防止出现匹配错误的问题,将所有标签字母都转换为小写存储起来即可。
map<string, vector<int>> mp;
mp[_key].push_back(line_num);
if (!_ids.empty())
mp[_ids].push_back(line_num);
在构造树结构时,我们在分配一个新的节点时,需要知道该节点暂时的父亲节点是哪一个。因此要是用于一个数据结构进行存储:
这里我们使用了last_floor数组记录,linenum是该节点的行数则有:
1、当一个层次为level的节点更新后
l a s t f l o o r [ l e v e l ] = l i n e n u m lastfloor[level]=linenum lastfloor[level]=