解题思路
题目所给出的结构化文档是一个树形结构,但由于其输入中使用的是小数点的数量来计算层次,因此我们可以构建一个结构体来存储每行的元素。
#include <string>
using namespace std;
struct Node
{
string name;//元素的标签,不区分大小写
int index;//元素的级数
string id;//元素的id,区分大小写
};
由于元素标签是不区分大小写的,因此不论是每个元素结构体的name,还是后面选择器的name查询,都统一使用一个函数来将整个字符串变成小写的,以省事。
void ToLow(string& a)
{
for (int i = 0; i < a.length(); i++)
{
if (a[i] >= 'A' && a[i] <= 'Z') a[i] += 32;
}
}
在输入的时候,根据题目的规则,依次将每一行的元素处理成一个结构体,如果没有id的话成员变量id就留空即可。使用一个数组储存这些结构体,在接下来选择器匹配的时候可以保证顺序不变从而模拟树形结构。
对于选择器的查询,关键点(难点)其实是多级选择器,如果只是单独一个选择器的话直接在上面那个元素结构体数组里尝试匹配即可。
这里就出现了一个史诗级误导:题目描述中,对于后代选择器的定义为:
复合表达式,格式为A B,其中A和B均为标签选择器或id选择器
这句话在中文上有两种解释:
- A和B都是标签选择器,或者A和B都是id选择器,即A与B的选择器类型一定相同。
- A可以是标签选择器或者是id选择器,B也可以是标签选择器或者是id选择器,A与B的选择器类型不一定相同。
卡在80分就是这里的问题了。结果显示csp官方对这道题这个定义的理解属于后者。(可第二个解释不是废话吗啊喂,总共就俩选择器啊)
那么如何匹配多级选择器呢?
首先我们使用一个vector来存储输入的选择器的各个单词,如果只有一个单词说明不是后代选择器,直接搜索就行了。如果这个vector数组的大小大于1,则说明是后代选择器了:
- 首先使用最靠后的那个单词进行查找,方式和上面一个单词的一样。
- 接下来从找到的位置往前遍历,设这个找到的位置为pos,根据树形结构在本题目中的定义,往前遍历到的第一个满足其index值为node[pos].index-1的结构体,来判断这个结构体是否匹配此时选择器在匹配的单词,因为只有这个结构体才是node[pos]的父亲节点,如果不匹配的话说明不符合条件。
- 如果是多级选择器,就按照上述方式,找pos的父亲,匹配,若匹配成功,找pos的父亲的父亲,继续匹配…只有最后整个选择器里所有的单词都匹配成功了,才算合格。
这道题其实不是很难,主要是匹配不要昏掉,然后就是上面那个后代选择器的定义的问题。
下面给出满分代码:
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
void ToLow(string& a)
{
for (int i = 0; i < a.length(); i++)
{
if (a[i] >= 'A' && a[i] <= 'Z') a[i] += 32;
}
}
struct Node
{
string name;
int index;
string id;
Node(string n, int i, string id) :name(n), index(i), id(id) { ToLow(name); }
};
vector<Node>node;
bool check(int pos, const vector<string>& cmd)//传入要检测的节点的位置,和需要用来检测的后代选择器数组
{
int index = node[pos].index;
int cur = cmd.size() - 2;
for (int i = pos - 1; i >= 0; i--)
{
if (node[i].index == index - 1)
{
if ((cmd[cur][0] == '#' && cmd[cur] == node[i].id) || (cmd[cur][0] != '#' && cmd[cur] == node[i].name)) cur--;
index = node[i].index;
if (cur < 0) return true;
}
}
return false;
}
int main()
{
int n, m;
cin >> n >> m;
cin.get();
for (int i = 0, index = 0; i < n; i++)
{
string input, name, id;
getline(cin, input);
for (int j = 0; j < input.length(); j++)
{
if (input[j] != '.')
{
index = j;
break;
}
}
size_t fi = input.find(' ', index);
if (fi != input.npos)
{
name = input.substr(index, fi - index);
id = input.substr(fi + 1);
}
else name = input.substr(index);
node.push_back(Node(name, index / 2, id));
}
for (int h = 0; h < m; h++)
{
string input;
getline(cin, input);
//先处理出选择器的列表,cmd大小为1时说明不是后代选择器
vector<string>cmd;
int l = -1, r = 0;
while(true)
{
r = input.find(' ', l + 1);
if (r != input.npos)
{
cmd.push_back(input.substr(l + 1, r - l - 1));
l = r;
}
else
{
cmd.push_back(input.substr(l + 1));
break;
}
}
//将所有的标签选择器全部改为小写字母
for (int i = 0; i < cmd.size(); i++)
if (cmd[i][0] != '#') ToLow(cmd[i]);
vector<int>ans;//答案数组
for (int i = 0; i < node.size(); i++)
{
bool OK = true;
if (cmd[cmd.size() - 1][0] == '#')
{
if (node[i].id == cmd.back())
{
if (cmd.size() == 1) ans.push_back(i + 1);
else if (check(i, cmd)) ans.push_back(i + 1);
}
}
else
{
if (node[i].name == cmd.back())
{
if (cmd.size() == 1) ans.push_back(i + 1);
else if (check(i, cmd)) ans.push_back(i + 1);
}
}
}
cout << ans.size();
for (int i = 0; i < ans.size(); i++)
{
cout << ' ' << ans[i];
}
cout << endl;
}
return 0;
}