题目很长,大家自行去官网看。
第三题还是一如既往的是大模拟,模拟css元素选择器,有接触过前端的同学对此不陌生了吧。
以前学css的时候就想过层叠样式表的实现,但是也没细究。ccf第三题有出过markdown转html的,我就预测ccf还会再出前端类的题目,那时候猜可能会是css,没想到真的出了。这些题外话了,还是讲回题目吧,这题难度不算高,但是细节决定成败。
思路:
结点类:
包含该结点的行数,标签名,id名,父结点(前继结点,存储父结点的意义是为了后面的查询,从后往前找,比从前往后找好,你想想每个结点有多个子节点,但只有唯一父结点,从前往后要找出对应的序列得嵌套多少次循环啊)和子节点列表
1.整理输入:先把输入的结构化文档由结点连成一棵树,每个结点要脸上树,就看前面有多少个点(点数/2==往下的级数),从根节点出发找到对应的父结点再连上
2.查找:每个查询都进行一次树的遍历(我用的是层次遍历),找到对应的选择器就开始往上找父元素。注意,选择器从后往前匹配,比如 div a h1, 就在遍历的时候找h1元素,从h1开始匹配,再往上找父元素a, div。再注意,这里的父元素,是所有祖先元素,往上一级找不到就继续往上一级找,直到选择器组都匹配完就记录这个结点的行数,次数加1。
3.细节:标签在结构化文档中和查询的选择器中大小写都有可能,而标签名大小写不敏感,所以遇到标签要化为小写。
新改动:
之前代码有个很难找的BUG,才90,现在终于找到问题所在了,终于AC100。这还得感谢我的一个朋友。
问题出在我的根结点,我的根结点的标签是“root”,理论上这个root不应该被匹配到,而且刚刚好ccf的测试用例的选择器也有root,所以选择上了。答案应该是0才对。解决方法:把根节点的标签(label)名换一个就是了,干脆空字符串"".
Java代码:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Scanner;
public class 元素选择器 {
static Scanner input = new Scanner(System.in);
//static Node root = new Node(0, null, "root", ""); 修改前 90
static Node root = new Node(0, null, "", ""); //修改后 100
public static void main(String[] args) {
int n = input.nextInt();
int m = input.nextInt();
//接收结构化文档,并整理好结构
input.nextLine();
getDateToTree(n);
//对每个选择器进行匹配,输出匹配结果
printAns(m);
}
//接收结构化文档,并整理成树结构
public static void getDateToTree(int n) {
for (int i = 0; i < n; i++) {
String[] line = input.nextLine().split(" ");
int dotCount = 0;
int idx = 0;
for (int j = 0; j < line[0].length() && line[0].charAt(j) == '.'; j++) {
dotCount++;
idx++;
}
//把标签转为小写
String label = line[0].substring(idx, line[0].length()).toLowerCase();
String id = line.length == 2 ? line[1] : "";
//指针每次从根结点开始找,找dotCount/2次
Node pointer = root;
for (int j = 0; j < dotCount / 2; j++)
pointer = pointer.subNodes.get(pointer.subNodes.size() - 1);
pointer.subNodes.add(new Node(i + 1, pointer, label, id));
}
}
//对每个选择器进行匹配,输出匹配结果
public static void printAns(int m) {
for (int i = 0; i < m; i++) {
//该列表存储的是选择器匹配到的标签们在结构化文档中的行数
ArrayList lineNums = new ArrayList<>();
String[] line = input.nextLine().split(" ");
match(lineNums, line);
lineNums.sort(Comparator.comparing(Integer::byteValue));
System.out.print(lineNums.size());
for (int lineNum : lineNums)
System.out.print(" " + lineNum);
System.out.println();
}
}
//选择器匹配,层次遍历结构树,对每个结点进行匹配
public static void match(ArrayList lineNums, String[] line) {
LinkedList queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node tem = queue.poll();
//从选择器最底端开始匹配
//如果选择器是标签就改为小写
String seleter = line[line.length - 1].charAt(0) == '#' ?
line[line.length - 1] : line[line.length - 1].toLowerCase();
if (seleter.equals(tem.label) || seleter.equals(tem.id)) {
int lineIdx = line.length - 2;
Node pointer = tem.preNode;
//从最下级开始往上找父元素
while (pointer != null && lineIdx >= 0) {
//如果选择器是标签就改为小写
seleter = line[lineIdx].charAt(0) == '#' ?
line[lineIdx] : line[lineIdx].toLowerCase();
if (pointer.label.equals(seleter) ||
pointer.id.equals(seleter)) {
lineIdx--;
}
pointer = pointer.preNode;
}
if (lineIdx == -1)
lineNums.add(tem.lineNum);
}
//子结点入队
for (Node val : tem.subNodes)
queue.offer(val);
}
}
}
//标签元素结点类
class Node {
int lineNum;//元素所在的行数
Node preNode;
ArrayList subNodes = new ArrayList<>();//子结点列表
String label;
String id;
Node(int lineNum, Node preNode, String label, String id) {
this.lineNum = lineNum;
this.preNode = preNode;
this.label = label;
this.id = id;
}
}
python代码:
# 标签元素结点类
class Node:
def __init__(self, lineNum, preNode, label, id):
self.lineNum = lineNum # 元素所在的行数
self.preNode = preNode # 父节点
self.label = label # 标签
self.id = id # id
self.subNodes = [] # 子结点列表
# 接收结构化文档,并整理成树结构
def get_date_to_tree(n, root):
for i in range(n):
line = input().split()
dotCount = 0
idx = 0
for j in range(len(line[0])):
if line[0][j] == '.':
dotCount += 1
else:
idx = j
break
# 把标签转为小写
label = line[0][idx:len(line[0])].lower()
id = line[1] if len(line) == 2 else ""
# 指针每次从根结点开始找,找dotCount/2次
pointer = root
for j in range(int(dotCount / 2)):
pointer = pointer.subNodes[len(pointer.subNodes) - 1]
pointer.subNodes.append(Node(i + 1, pointer, label, id))
# 对每个选择器进行匹配,输出匹配结果
def print_ans(m, root):
for i in range(m):
# 该列表存储的是选择器匹配到的标签们在结构化文档中的行数
lineNums = []
line = input().split()
match(lineNums, line, root)
print(len(lineNums), end=' ')
for lineNum in sorted(lineNums):
print(lineNum, end=' ')
print()
# 选择器匹配,层次遍历结构树,对每个结点进行匹配
def match(lineNums, line, root):
queue = []
queue.append(root)
while queue:
tem = queue.pop(0)
# 从选择器最底端开始匹配
# 如果选择器是标签就改为小写
seleter = line[len(line) - 1] if line[len(line) - 1][0] == '#' else \
line[len(line) - 1].lower()
if seleter == tem.label or seleter == tem.id:
lineIdx = len(line) - 2
pointer = tem.preNode
# 从最下级开始往上找父元素
while (pointer != None and lineIdx >= 0):
# 如果选择器是标签就改为小写
seleter = line[lineIdx] if line[lineIdx][0] == '#' else \
line[lineIdx].lower()
if (pointer.label == seleter or
pointer.id == seleter):
lineIdx -= 1
pointer = pointer.preNode
if (lineIdx == -1):
lineNums.append(tem.lineNum)
# 子节点入队
for val in tem.subNodes:
queue.append(val)
# 主程序逻辑
# root = Node(0, None, "root", "") 修改前 90
root = Node(0, None, "", "") # 修改后 100
n, m = [int(val) for val in input().split()]
# 接收结构化文档,并整理好结构
get_date_to_tree(n, root)
# 对每个选择器进行匹配,输出匹配结果
print_ans(m, root)