CCF CSP真题——201809 T3 元素选择器

题目概述

原题连接
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

输入样例

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]=linenum
2、第lienum行层次为level的father更新:
f a t h e r = l a s t f l o o r [ l e v e l − 1 ] father=lastfloor[level-1] father=lastfloor[level1]

模块2、输入查询的要求,查询到要选择的行数
由于在前面的存储部分我们将很多问题已经分析解决的比较简便,在查询部分我们只需要关注两个问题:标签和属性的分辨和如何处理序列的查询。
1、如何分辨标签和属性
标签和属性的分辨有一种比较简练的方式,就是在存储时将属性部分的#存放进去,然后将两种类型按照相同的字符串匹配,每次在一个节点进行匹配时,将要匹配的string和标签属性两个部分都进行匹配,有一个能够匹配上就说明该节点符合要求。
2、对复合序列的处理
序列的查询是很多个标签、属性的混合,需要将其分割开然后逐层的向上匹配。这里的核心思想是将分割后的多段字符串放入vector之中,然后从最末位向上逐层匹配,使用index标识匹配到的位置。先使用map查到匹配的最后一个字符串所在的行数vector后,遍历vector中的每个行,依次向上匹配。
匹配的递归思路如下:
1、如果该层能够匹配上,index-1,向father移动;
2、匹配不上,index不变,向father移动;
3、匹配到了根节点还没匹配上,则说明该行不被选取,考虑下一个;
4、匹配到某个点index==-1,也就是说整个序列都匹配完成,则将答案加入到答案vector,考虑下一个;
具体实现函数设计如下:

bool back_find(int line_flag, vector<string> v, int index)
{
    
    
	if (index == -1) return true;
	if (line_flag == -1) return false;
	if (list[line_flag]._tag == v[index] || list[line_flag]._id == v[index])
		return back_find(list[line_flag].father, v, index - 1);
	else
		return back_find(list[line_flag].father, v, index);
}

实现源码

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
const int M = 105;
struct node
{
    
    
	string _tag;//标签
	string _id;//属性id
	int father;//父节点的行数
	node()
	{
    
    
		_tag.clear();
		_id.clear();
		father = 0;
	}
};
node list[M];
map<string, vector<int>> mp;
int last_floor[M];//存放的是每一层的上一个节点的行数,方便分配父亲节点
void make_list(string s, int line_num)
{
    
    
	int level = 0;
	int temp = 0;
	string _key; string _ids;
	_key.clear(); _ids.clear();
	for (int i = 0; i < s.size(); i++)
	{
    
    
		temp = 0;
		while (s[i] == '.')
		{
    
    
			temp++;
			i += 2;
		}
		if (temp > 0)
		{
    
    
			i--;
			level = temp;
			continue;
		}
		if (s[i] == ' ')
		{
    
    
			_key = _ids;
			_ids.clear();
			continue;
		}
		_ids += s[i];
	}
	if (_key.empty())
	{
    
    
		_key = _ids;
		_ids.clear();
	}
	for (int i = 0; i < _key.size(); i++)
	{
    
    
		if (_key[i] >= 'A' && _key[i] <= 'Z')
			_key[i] += 32;
	}
	list[line_num]._tag = _key;
	list[line_num]._id = _ids;
	last_floor[level] = line_num;
	if (level > 0)
		list[line_num].father = last_floor[level - 1];
	else
		list[line_num].father = -1;
	mp[_key].push_back(line_num);
	if (!_ids.empty())
		mp[_ids].push_back(line_num);
}
bool back_find(int line_flag, vector<string> v, int index)
{
    
    
	if (index == -1) return true;
	if (line_flag == -1) return false;
	if (list[line_flag]._tag == v[index] || list[line_flag]._id == v[index])
		return back_find(list[line_flag].father, v, index - 1);
	else
		return back_find(list[line_flag].father, v, index);
}
void search_pos(string s)
{
    
    
	vector<string> vec; vec.clear();
	string tools; tools.clear();
	for (int i = 0; i < s.size(); i++)
	{
    
    
		if (s[i] == ' ')
		{
    
    
			if (tools[0] != '#')
			{
    
    
				for (int j = 0; j < tools.size(); j++)
				{
    
    
					if (tools[j] >= 'A' && tools[j] <= 'Z')
						tools[j] += 32;
				}
			}
			vec.push_back(tools);
			tools.clear();
			continue;
		}
		tools += s[i];
	}
	if (tools[0] != '#')
	{
    
    
		for (int j = 0; j < tools.size(); j++)
		{
    
    
			if (tools[j] >= 'A' && tools[j] <= 'Z')
				tools[j] += 32;
		}
	}
	vec.push_back(tools);
	vector<int> ans; ans.clear();
	for (int i = 0; i < mp[vec[vec.size() - 1]].size(); i++)
	{
    
    
		if (back_find(mp[vec[vec.size() - 1]][i], vec, vec.size() - 1) == true)
			ans.push_back(mp[vec[vec.size() - 1]][i]);
	}
	printf("%d", ans.size());
	for (int i = 0; i < ans.size(); i++)
	{
    
    
		printf(" %d", ans[i]);
	}
	printf("\n");
}
int main()
{
    
    
	int datagroup = 0;
	int list_line = 0;
	int line_num = 0;
	string s;
	scanf("%d %d", &list_line, &datagroup);
	getchar();
	for (int i = 0; i < list_line; i++)
	{
    
    
		s.clear();
		getline(cin, s);
		line_num++;
		make_list(s, line_num);
	}
	for (int i = 0; i < datagroup; i++)
	{
    
    
		s.clear();
		getline(cin, s);
		search_pos(s);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_43942251/article/details/106634207