分析:
这题超级坑,当时考试时只得了50分,现在重新做一直卡在80分,各种复杂情况都考虑到了,还是不能ac,于是尝试三种不同办法解决,也均不能AC,最后发现是题目写错了。
也就是说后代选择器只能是多个id或者是多个label,不会出现二者都有的情况,然而ccf后台的测试数据最后两个用例就是二者的嵌套,超级坑。
相反,很多复杂的情况用例并未考虑,以至于很多很水的代码都可以ac。
下面介绍本题解题思路。
第一步,弄清题目让我们干什么。
题目需要我们写一个选择器,不论输入的是标签,id还是后代选择器,都能得到文档中所有被选择的行以及行的个数。
第二步,确定存储结构。
一般大模拟都是需要定义一个结构体来存储输入文档,结构体有几个成员变量代表我们将输入的一行行字符串怎么切割,切割成几部分。既然文档由一些点,标签,id组成,点代表级数,那么至少我们应该存储层级,label和id,我在代码里还加了一个变量-father,代表某一行父结点的行数。
不设置father也可以,但将复杂很多,为什么呢?在实行后代选择器时,一个结点有多个不同级别的后代,我们遍历这颗树,迭代写法的话需要不停回溯,递归写法也就是dfs超级消耗时间和空间。我最后通过的代码用时几十毫秒,然而dfs则花费数秒。(这些解法我都实现了,但是鉴于遍历回溯时很复杂,这里先不赘述),那么设置了father会怎样呢,一个结点可以有多个孩子,却只有唯一的父结点,我们不断延父结点往上遍历,很快就能找到结果。
结构体构造完了,文档各行就构成了一个向量,向量的下标与行数有关。
第三步,处理输入的文档。
这里要注意,如果用cin直接读入一行,它固然会遇见回车就截止读入,但是本题文档各行可能含有空格,cin遇见空格也是会终止读入的,我们采用getline(cin,s)读入一行,这里有个常见的错误就是往往主函数最开始读入cin>>n>>m;然后紧接着一个换行再读入文档,那么getline第一个读入的就是回车,所以我们在读入文档前一般用getchar()处理掉回车。
处理文档各行:各行之前的点数除以二代表层级数,各行往上遍历找到的第一个层级高于它的就是它的父结点,再用若干行代码将各行的id和label分别存进结构体就ok了,处理文档比较简单。
第四步,编写元素选择器。
下面处理要求查询的各个元素选择器。这里一个技巧是遍历字符串,遇见空格或者字符串末尾就终止,如果到字符串结尾才终止,则不含空格,也就是单一的id或者label选择器,直接与doc各行比较即可,否则就是后代选择器。
处理后代选择器时,注意一定要将选择器原样存进向量,当然除掉空格,从最后的选择器进行比较,不等就往上继续,相等则文档指针继续向父结点回溯,选择器指针也减减,注意,此时语句二者不等不可停止回溯,因为后代选择器只要它的祖先结点包含那个选择器就可以了,而不一定要是父结点,一直回溯到最上层的结点才终止。
最后要注意的是标签不区分大小写,而id区分,即使在二者混杂的后代选择器中也要分开处理,将标签一律转化为小写;另一个即使处理后代选择器时我们是自下而上,自后向前,所以需要将存储几个的向量倒着输出。
#include <iostream>
#include <vector>
#include <cctype>
using namespace std;
struct html{
int level,father;//层级和父结点行号
string label,id;
};
vector<html> doc;//文档向量
vector<int> num;//结果向量
int main(){
int n,m,fa,le;
cin>>n>>m;
getchar();
while(n--){//处理文档
string s,la,id;
getline(cin,s);
int p = 0;
while(s[p] == '.') p++;
le = p / 2;
if(p == 0 || !doc.size()) fa = -1 ;
else{
for(int i = doc.size() - 1;i >= 0;i--){
if(doc[i].level == le - 1){
fa = i;
break;
}
}
}
bool flag = false;
while(p < s.size()){
if(s[p] == ' '){
p++;
continue;
}
if(s[p] == '#'){
flag = true;
id += s[p];
p++;
continue;
}
if(flag) id += s[p];
else la += tolower(s[p]);
p++;
}
doc.push_back({le,fa,la,id});
}
while(m--){//选择
string s;
getline(cin,s);
int p = 0;
while(p != s.size() - 1 && s[p] != ' ') p++;
if(p == s.size() - 1){//单一选择器
if(s[0] == '#'){
for(int i = 0;i < doc.size();i++){
if(s == doc[i].id) num.push_back(i+1);
}
}
else{
for(int i = 0;i < s.size();i++) s[i] = tolower(s[i]);
for(int i = 0;i < doc.size();i++){
if(s == doc[i].label) num.push_back(i+1);
}
}
cout<<num.size()<<" ";
for(int i = 0;i < num.size();i++) cout<<num[i]<<" ";
}
else{
int j = 0;
vector<string> v;
p = 0;
bool flag = true;
while(p < s.size()){
if(s[p] == '#') flag = false;
if(s[p] == ' '){
flag = true;//还原标志变量
v.push_back(s.substr(j,p-j));
j = p + 1;
}
if(flag) s[p] = tolower(s[p]);
p++;
}
v.push_back(s.substr(j,p-j));
for(int i = doc.size() - 1;i > 0;i--){
int q = v.size() - 1;
if(v[q] != doc[i].label && v[q] != doc[i].id) continue;
p = doc[i].father,q--;
while(q >= 0 && p >= 0){
if(doc[p].label == v[q] || doc[p].id == v[q]) q--;
p = doc[p].father;
}
if(q == -1) num.push_back(i+1);
}
cout<<num.size()<<" ";
for(int i = num.size()-1;i >= 0;i--) cout<<num[i]<<" ";
}
cout<<endl;
num.clear();
}
return 0;
}