CCF认证 201809-3元素选择器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/richenyunqi/article/details/85251149

欢迎访问我的CCF认证考试题解目录哦 https://blog.csdn.net/richenyunqi/article/details/83385502

题目描述

CCF认证 201809-3元素选择器题目描述

算法设计

CCF认证考试的第3题一向是大模拟题,这次也不例外。遇到这样的题目一定不要害怕,这样的题目涉及的算法不多,考的主要是代码量和细心程度。考试前一定要做一下相关题目,开始可以先按照别人的代码,一步步理解,然后自己独立实现一遍,最终能到自己编写代码能够AC的情况。一定要自己敲代码,一定要自己敲代码,一定要自己敲代码,重要的事情说三遍,哪怕是按照别人的算法,也一定要自己实现一遍。
首先建立一个结构体element用来表示每个元素,由于元素最多有100个,可以用vector<element>elements(105)来表示所有元素,代码如下:

struct element{
    int father;//父元素所在行号
    string label,id;//标签、id属性
};
vector<element>elements(105);//从下标1开始存储所有元素,下标代表每个元素所在行号

由于每个元素都有它的级数,可以用vector<vector<int>>series来存储每个元素的级数。这是个二维数组,第一维下标表示级数(级数从0开始),第二维下标没有用处,数组内容表示元素所在行号,正好可以与elements的下标对应起来。例如:假设series[0][2]=3,表示行号为3的元素的级数为0,可以在elements[3]找到这个元素的相关信息。
数据结构选好了,接下来就是按照要求进行模拟了,由于要求非常多,一定要细心,一步步,一点点实现,不要贪快。可以一边跟着我下面的叙述,一边随手敲着代码。

读取输入元素数据

首先是读取元素,并进行分割。前面说过了,这样的大模拟题不考算法,考的是代码量和细心程度,所以时间往往卡的不紧,所以我更推荐使用C++自带的string类型,里面封装好了许多常用的函数,不推荐用char数组。
可以用C++的getline函数读取一行字符串,然后针对这行字符串进行分割。我们需要通过这行字符串获取的信息分别是:行号、级数、标签、id属性、父元素。

  1. 行号:可以定义一个自变量 i i 初始化为1 ,每读取一行字符串递增1次,那么 i i 就是每个元素的行号
  2. 级数可以通过字符串开始的小数点数量来得到。对于读取到的字符串line,可以定义一个自变量 j j 初始化为0,利用下面的代码来求:
while(line[j]=='.')
    ++j;

循环结束后, j j 就表示小数点数量。那么 j / 2 j/2 就表示级数。那么就可以把当前元素所在行号压入series中了,代码如下:

if(series.size()<=j/2)//series还没有下标为j/2的元素
            series.push_back({i});//压入一个初始化为当前元素所在行号的vector
        else//series有下标为j/2的元素
            series[j/2].push_back(i);//直接把行号压入
  1. 标签和id属性: j j 就表示小数点数量,那么line[j]就表示第一个非小数点的字符。可以定义一个自变量 k k 初始化为 j j ,利用下面的代码:
for(k=j;k<line.size()&&line[k]!=' ';++k);

循环结束后, k k 就表示字符串末尾或字符串中空格位置。就可以利用C++ string 的截取字符串函数substr(起始字符位置,截取的字符个数)来求标签和id属性。即标签为line.substr(j,k-j),当k为字符串末尾时,id属性为空字符串;当k不上字符串末尾,即k为字符串空格位置时,id属性为line.substr(k+1)。即id属性可以表示为(k==line.size())?"":line.substr(k+1)

  1. 父元素:知道了当前元素的级数 j / 2 j/2 ,那么它的父元素就应该是级数 j / 2 1 j/2-1 的元素中离它最近的一个,即series[j/2-1].back()。当前,在当前元素是第一行的元素时,其级数为0,可以让它的父元素为-1。求父元素行号代码如下:
elements[i].father=j/2-1<0?-1:series[j/2-1].back();//求父元素所在行号
  1. 标签大小写不敏感:可以把标签中的英文字符都变为小写字符,在读取查询元素时,也把标签中的英文字符都变为小写字符,这样就可以保证标签大小写不敏感。代码非常简单,可以自己尝试写一下。

综上,读取数据的全部代码如下:

    for(int i=1;i<=n;++i){
        getline(cin,line);
        int j=0,k=0;
        while(line[j]=='.')//循环结束后j就表示小数点数量,line[j]表示第一个非小数点的字符
            ++j;
        for(k=j;k<line.size()&&line[k]!=' ';++k);//循环结束后,k就表示字符串末尾或字符串中空格位置
        if(series.size()<=j/2)//series还没有下标为j/2的元素
            series.push_back({i});//压入一个初始化为当前元素所在行号的vector
        else//series有下标为j/2的元素
            series[j/2].push_back(i);//直接把行号压入
        elements[i].label=line.substr(j,k-j);//取标签字符串
        for(auto&c:elements[i].label)//将元素的标签中的英文字符都变为小写字符
            c=tolower(c);
        elements[i].id=(k==line.size())?"":line.substr(k+1);//取id属性字符串
        elements[i].father=j/2-1<0?-1:series[j/2-1].back();//求父元素所在行号
    }

读取查询元素数据

同样是C++的getline函数读取一行字符串,然后针对这行字符串按空格进行分割,将分割后的字符串存储到vector<string>query。有了前面分割字符串的基础,读者可以自己尝试一下编写实现以上要求的代码。注意对于读取的标签(即字符串首字符不是#),要把标签中的英文字符都变为小写字符。自己写完后可以参考我在最后的AC整道题的C++代码中的相关实现。

进行查找

经过漫长的准备工作,终于来到了解决整道题目的最终步骤——实现查找功能。首先定义vector<int>ans来存储每一次查询结果的行号。要注意一点:对于后代选择器A B,A是B的一个祖先元素即可,而不必一定是B的父元素
我们将查询的选择器已经存储在了vector<string>query中,那么query所含元素个数-1即为查询最低元素的最小级数,例如:对于A A B的选择器,query所含元素个数为3,那么B所在级数至少为2,以下以A A B形式的选择器为例。根据这个性质,遍历series[query.size()-1]到series末尾的所有元素,找出其中标签或id等于B的元素,查找其祖先元素是否包含两个A,如果包含,说明当前找到的元素符合要求,将其行号放入ans中;如果不包含,继续遍历。
最终,ans的长度即为符合要求的元素个数,ans包含的元素即为符合要求的元素行号。具体实现可参考如下代码:

	for(int i=query.size()-1;i<series.size();++i)//从query.size()-1开始遍历
            for(int j:series[i])//遍历当前series[i]中的元素行号
                if(elements[j].label==query.back()||elements[j].id==query.back()){//如果标签或id等于最低查询元素
                    int k=query.size()-2;//k指向查询的倒数第2个元素
                    for(int p=elements[j].father;k>=0&&p!=-1;p=elements[p].father)//遍历行号为p元素的祖先元素行号
                        if(elements[p].label==query[k]||elements[p].id==query[k])//如果与当前查询的元素相同
                            --k;//继续查询前面的元素
                    if(k<0)//所有元素均已查询到,该元素符合要求
                        ans.push_back(j);//压入ans中
                }
        printf("%d ",ans.size());//输出
        for(auto i:ans)
            printf("%d ",i);
        puts("");//换行

C++代码

#include<bits/stdc++.h>
using namespace std;
struct element{
    int father;//父元素所在行号
    string label,id;//标签、id属性
};
vector<element>elements(105);//从下标1开始存储所有元素,下标代表每个元素所在行号
vector<vector<int>>series;
int main(){
    int n,m;
    string line;
    scanf("%d%d%*c",&n,&m);
    for(int i=1;i<=n;++i){
        getline(cin,line);
        int j=0,k=0;
        while(line[j]=='.')//循环结束后j就表示小数点数量,line[j]表示第一个非小数点的字符
            ++j;
        for(k=j;k<line.size()&&line[k]!=' ';++k);//循环结束后,k就表示字符串末尾或字符串中空格位置
        if(series.size()<=j/2)//series还没有下标为j/2的元素
            series.push_back({i});//压入一个初始化为当前元素所在行号的vector
        else//series有下标为j/2的元素
            series[j/2].push_back(i);//直接把行号压入
        elements[i].label=line.substr(j,k-j);//取标签字符串
        for(auto&c:elements[i].label)//将元素的标签中的英文字符都变为小写字符
            c=tolower(c);
        elements[i].id=(k==line.size())?"":line.substr(k+1);//取id属性字符串
        elements[i].father=j/2-1<0?-1:series[j/2-1].back();//求父元素所在行号
    }
    while(m--){
        getline(cin,line);
        vector<string>query;//存储查询的元素
        for(int i=0;i<line.size();++i){//遍历读取的一行字符串
            int j=i;
            while(j<line.size()&&line[j]!=' ')
                ++j;
            string s=line.substr(i,j-i);
            if(s[0]!='#')//将标签中所有字符转换为小写字符
                for(auto&c:s)
                    c=tolower(c);
            query.push_back(s);
            i=j;
        }
        vector<int>ans;
        for(int i=query.size()-1;i<series.size();++i)//从query.size()-1开始遍历
            for(int j:series[i])//遍历当前series[i]中的元素行号
                if(elements[j].label==query.back()||elements[j].id==query.back()){//如果标签或id等于最低查询元素
                    int k=query.size()-2;//k指向查询的倒数第2个元素
                    for(int p=elements[j].father;k>=0&&p!=-1;p=elements[p].father)//遍历行号为p元素的祖先元素行号
                        if(elements[p].label==query[k]||elements[p].id==query[k])//如果与当前查询的元素相同
                            --k;//继续查询前面的元素
                    if(k<0)//所有元素均已查询到,该元素符合要求
                        ans.push_back(j);//压入ans中
                }
        printf("%d ",ans.size());//输出
        for(auto i:ans)
            printf("%d ",i);
        puts("");//换行
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/richenyunqi/article/details/85251149