问题描述
【题目背景】
层叠样式表( Cascading Style Sheets,缩写CSS)是一种用来为结构化文档(如HTML文档)添加样式(字体、间距和颜色等)的计算机语言。例如,对于以下的HTML文档:
<html>
<title>Sample</title>
</head>
<h1>He11o</h1>
<p id="subtitle">Greetings</div>
<p>Hello, world!</p>
</body>
</htm1>
配合以下CSS片段可以为其屮的标题和段落设置相应的格式
h1 { font-weight: bold; }
#subtitle { font-size: 12px; }
这段CSS片段为前而HTML文档添加了样式,使得标题“Hello”(第6行<h1>和</h1>标签之间的内容)具有粗体,使得段落“ Greetings”(第7行<pid=" subtie1e"和<\p>标签之间的内容)具有12个像素的字体大小。这里,CSS片段第1行屮出现的h1是一个选择器,它选屮了HTML文档第6行的h1元素。CSS片段第2行屮出现的# subtitle也是一个选择器,选屮了HTML文档第7行id属性为 subtil1e的p元素。注意它并没有选中HTML文档第8行不带属性的p元素。
【题目描述】
本题要实现一个简化版的元素选择器。给出一个结构化文档,和若干个选择器,对每个选择器找出文档屮所对应选屮的元素。
结构化文档 结构化文档由元素组成,一个元素可以包含若干个子元素(可以没有)。一个文档有一个根元素,在整体上形成树的结构。以下是本题结构化文档的一个例子:
html
..head
....title
..body
....h1
....p #subtitle
....div #main
......h2
......p #one
......div
........p #two
- 文档屮每行表示一个元素,元素的标签由一个或者多个字母或数字组成。标签大小写不敏感,例如div、Div、DIV都是同一类标签。
- 元素可以附加一个id属性,屈性值也是由一个或者多个字母或数字组成,之前有一个井号#。id属性大小写敏感,例如a和A是两个不同的id。如果元素有id属性,标签和屈性之间用一个空格字符分隔。
- 标签之前的缩进表示元素之间的包含关系:一个元素E所在行之后连续的缩进更深的行代表的元素是元素E的后代元素,其屮缩进恰好深一层的是元素E的子元素。为了便于观察,每一级缩进用两个小数点符号…表示
选择器 本题屮会出现的选择器有三种,分别为:
- 标签选择器:用标签来表示。例如p表示选择标签为p的所有元素。
- id选择器:用id属性来表示。例如# main表示选择id属性为main的元素。题目保证文档屮不同的元素不会有相同的id属性
- 后代选择器:复合表达式,格式为AB,其屮A和B均为标签选择器或i选择器,屮间用一个空格字符分隔,表示选择满足选择器B的所有元素,且满足这些元素有祖先元素满足选择器A。例如,选择器divp在上而的文档屮会选屮最后一行的元素p,但不会选屮i属性为 subtil1e的那个元素p。注意,后代选择器可以有更多的组成部分构成,divp是一个两级的后代选择器,而divdivp则是一个三级的后代选择器
【输入格式】
输入第一行是两个正整数n和m,分别表示结构化文档的行数,和待查询的选择器的个数,屮间用一个空格字符分隔
第2行全第n+1行逐行给出结构化文档的内容
第n+2行全第n+m+1行每行给出一个待查询的选择器。记第n+1+i行的选择器为s,1≤i≤m
【输出格式】
渝出共m行,每行有若干个整数。第i行表示选择器s;选屮的结果(1≤i≤m)其屮第一个整数n表示s选中的元素个数。随后n个整数,分别表示选屮元素在结构化文档屮出现的行号(行号从1开始编号)。行号按从小到大排序,相邻整数之间用个空格字符分隔。
【样例输入】
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
【样例解释】
对于样例屮查询的5个选择器:
1.p选屮所有的元素p
2.# subtil1e选屮第6行id属性为 subtitle的元素p
3.由于没有标签为h3的元素,因此h3没有选屮任何元素
4.第9行和第11的p元素都有祖先是div元素,而第6行的p元素没有祖先是div元素;
5. div divp要求选屮的p元素有两级祖先都是div元素,只有第11行的p元素满足这个条件。
数据规模和约定
- 1≤n≤100
- 1≤m≤10
- 结构化文档和待査询的选择器每行长度不超过80个字符(不包括换行符)
- 保证输入的结构化文档和待查询的选择器都是合法的
测试点 | 结构化文档级数 | id属性 | 待查询选择器的类型 |
---|---|---|---|
1 | 1 | 无 | 标签 |
2 | 2 | 无 | 标签 |
3 | 2 | 有 | 标签、id |
4 | 2 | 无 | 无标签、后代(两级,不含id) |
5 | >2 | 无 | 标签 |
6 | >2 | 有 | 标签、id |
7 | >2 | 无 | 「标签、后代(两级,不含id) |
8 | >2 | 有 | 标签、id、后代(两级) |
9 | >2 | 无 | 标签、后代(多级,不含id) |
10 | >2 | 有 | 标签、id、后代(多级) |
【提示】
多级的后代选择器在匹配时,可以采用贪心的策咯:除最后一级外,前而的部分都可以尽量匹配层级小的元素
解题思路
为了简化查找过程,可以用字符串哈希来代替名称中的字符串。可以先将输入的所有元素都存储下来,每次发起查询操作时,按照输入元素的顺序依次顺序查找。每个元素可以通过标签或ID找到,因此每个元素可以用包含三个整数的结构体来表示,存储标签hash、id的hash以及这个元素所在行的深度。
在查找操作中,由于我们知道了每一行元素的所在深度,而且下一行元素的深度的范围只能在0到上一行深度加一之内,我们只需维护一个当前路径vector,每移到下一个元素时,在当前路径中修改这一个元素所在深度的路径,然后进行判断操作。
判断当前元素是否符合选择器的条件,不仅仅需要判断当前元素和选择器中的最后一个元素是否匹配,还需要判断在多级选择下,当前元素前的路径是否符合选择器中前面的限制条件。因此在判断操作中,首先沿着当前路径搜索,如果路径搜索中,当前元素满足当前选择器的条件,那么在下一次的路径搜索中,就判断是否满足选择器的下一个条件,直到搜索完当前元素的前面路径的元素或已经搜索完选择器中最后一个条件的前面的条件。
如果已经搜索完选择器中最后一个条件的前面的条件,那么可以进一步判断当前元素和选择器中的最后一个元素是否匹配,如果是的话,当前元素就是要查找的元素,将这个元素的行数加入到存储结果的vector中。在完成所有的查找后,主函数依次输出结果vector中的目标元素行数。
程序源码
#include<iostream>
#include<vector>
#include<string>
using namespace std;
struct element{ //元素结构体
int tag; //标签
int id; //ID
int layer; //层数
element(int tag, int layer) :tag(tag), layer(layer) {
id = 0;
}
};
int calcHash(string& s) { //计算字符串哈希
int i = 0,rs = 0,base = 1;
if (s[0] == '#') { //id,区分大小写
for (; i <= s.size(); i++) {
rs += base * s[i];
base *= 7;
}
return rs;
}
while (s[i] == '.') i++; //标签名,不区分大小写
for (; i <= s.size(); i++) {
rs += base * tolower(s[i]); //转换为小写
base *= 7;
}
return rs;
}
int getLevel(string& s) { //获取元素的深度
int count=0;
while (s[count] == '.') count++;
return count / 2;
}
void search(vector<element>& lines, vector<int> target,vector<int>& result) { //查找匹配的行,传入 所有行 选择器 查找结果 的引用
vector<element*> history; //当前元素的路径
history.resize(lines.size());
int level = -1; //当前元素所在深度
for (int i = 0; i < lines.size(); i++) { //依次判断所有的行
level = lines[i].layer; //修改当前元素深度
history[level] = &lines[i]; //在路径上加上当前元素
int a = 0, b = 0;
for (; a < level; a++) { //判断依次前面的路径是否满足选择器的条件
if (b == target.size() - 1) break; //已经满足选择器的条件,只需判断最深一级的元素是否满足要求
if (history[a]->id == target[b] || history[a]->tag == target[b]) b++; //路径中的某个元素满足选择器中的条件,接下来判断选择器中的下一个元素
}
if (b == target.size() - 1) { //前面的路径满足选择器的条件,断最深一级的元素是否满足要求
if (history[level]->id == target[b] || history[level]->tag == target[b]) { //满足要求
result.push_back(i + 1); //结果里添上当前行数
}
}
}
}
int main() {
string s;
int n, m;
cin >> n >> m;
vector<element> lines; //按输入顺序存储输入的元素
for (int i = 0; i < n; i++) {
cin >> s;
lines.push_back(element(calcHash(s), getLevel(s))); //新增行
if (getchar() == ' ') { //后面还有id
cin >> s;
lines.back().id = calcHash(s); //计算hash设置id
}
}
for (int i = 0; i < m; i++) { //查询操作
vector<int> targetHash, resultLine; //目标hash,结果数组
do {
cin >> s; //获取当前行的所有选择器
targetHash.push_back(calcHash(s)); //计算hash,加入targetHash
} while (getchar() == ' ');
search(lines, targetHash, resultLine); //查找目标行
cout << resultLine.size() << " "; //输出找到的目标行
for (int i = 0; i < resultLine.size(); i++) cout << resultLine[i] << " ";
cout << endl;
}
return 0;
}