CCF 201809-3元素选择器

分析:

这题超级坑,当时考试时只得了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;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值