2017年9月CCF真题题解

1.201709-2

问题描述

  有一个学校的老师共用 N个教室,按照规定,所有的钥匙都必须放在公共钥匙盒里,老师不能带钥匙回家。每次老师上课前,都从公共钥匙盒里找到自己上课的教室的钥匙去开门,上完课后,再将钥匙放回到钥匙盒中。
  钥匙盒一共有 N个挂钩,从左到右排成一排,用来挂 N个教室的钥匙。一串钥匙没有固定的悬挂位置,但钥匙上有标识,所以老师们不会弄混钥匙。
  每次取钥匙的时候,老师们都会找到自己所需要的钥匙将其取走,而不会移动其他钥匙。每次还钥匙的时候,还钥匙的老师会找到最左边的空的挂钩,将钥匙挂在这个挂钩上。如果有多位老师还钥匙,则他们按钥匙编号从小到大的顺序还。如果同一时刻既有老师还钥匙又有老师取钥匙,则老师们会先将钥匙全还回去再取出。
  今天开始的时候钥匙是按编号从小到大的顺序放在钥匙盒里的。有 K位老师要上课,给出每位老师所需要的钥匙、开始上课的时间和上课的时长,假设下课时间就是还钥匙时间,请问最终钥匙盒里面钥匙的顺序是怎样的?
输入格式
  输入的第一行包含两个整数 N, K
  接下来 K行,每行三个整数 w, s, c,分别表示一位老师要使用的钥匙编号、开始上课的时间和上课的时长。可能有多位老师使用同一把钥匙,但是老师使用钥匙的时间不会重叠。
  保证输入数据满足输入格式,你不用检查数据合法性。
输出格式
  输出一行,包含 N个整数,相邻整数间用一个空格分隔,依次表示每个挂钩上挂的钥匙编号。
样例输入
5 2
4 3 3
2 2 7
样例输出
1 4 3 2 5
样例说明
  第一位老师从时刻3开始使用4号教室的钥匙,使用3单位时间,所以在时刻6还钥匙。第二位老师从时刻2开始使用钥匙,使用7单位时间,所以在时刻9还钥匙。
  每个关键时刻后的钥匙状态如下(X表示空):
  时刻2后为1X345;
  时刻3后为1X3X5;
  时刻6后为143X5;
  时刻9后为14325。
样例输入
5 7
1 1 14
3 3 12
1 15 12
2 7 20
3 18 12
4 21 19
5 30 9
样例输出
1 2 3 5 4
评测用例规模与约定
  对于30%的评测用例,1 ≤ N, K ≤ 10, 1 ≤ wN, 1 ≤ s, c ≤ 30;
  对于60%的评测用例,1 ≤ N, K ≤ 50,1 ≤ wN,1 ≤ s ≤ 300,1 ≤ c ≤ 50;
  对于所有评测用例,1 ≤ N, K ≤ 1000,1 ≤ wN,1 ≤ s ≤ 10000,1 ≤ c ≤ 100。

考点:STL模拟解题
易错:对题中过程的理解。数组越界。
思路:
在这里,博主介绍两种思路给大家。
      第一种思路,这条思路是博主第一次接触时想到的。首先理清解读题意。题目要我们解决如下的问题。第一,我们如何存储给定的数据,在这里我们很容易想到利用结构体来存储w,s,c,好那么我们暂定结构体。第二,题目中的过程要如何模拟。按照题目的意思,我们要实现还钥匙和取钥匙两种操作,并且在两者冲突时,先还再取,所以我们还必须设置一个bool值来判断一下执行取操作还是还操作,在结构体中,我们加入一个bool值作为判断用。题中还提到了一个元素——时间,我们要根据时间来执行操作,我们自然要设置一个计时的变量,这个计时变量的初值为所有结构体数组中min(s)。

       前期准备工作做完,我们开始模拟操作。取操作即为遍历数组,当给定数字等于数组中的某个值时,将数组该位置置0即可,注意,这里的查找操作不可以用二分或者其他,因为数字的顺序等因素都是不确定的。存操作即为找到数组中第一个为0的值,按照钥匙编号大小顺序,存入即可。

      事实证明楼主的第一种想法实在复杂,楼主写了比较久的时间--|||,同时也提醒大家,A题目时有一个好的逻辑思路是有多么重要!!!下面给大家介绍另外一种比较简便的思路。

       我们重新阅读题目可以发现,题目中出现了较多比较操作的暗示,比如按照钥匙标号还,先还后取等,而且我们也是按照时间的先后顺序执行取存操作的,所以这道题可不可以考虑用排序的思路来做呢?我们可以试一试,按照题目的要求,假如我们用结构体数组排序,那么首先肯定按start_time即开始时来排序,那么如果时间相同,对于取操作来说没有影响,存操作就要按照w的先后顺序来执行,这里我们又得出一个排序的依据,那么既有取操作,又有存操作的时候,该怎么办呢?我们很容易想到可以设置一个优先级变量来区别取和存,0和1是在合适不过啦!综上,我们得出一下结论:

(1)第一排序,按照start_time;每一个老师的取操作都对应着一个存操作,所以会有2*K个操作及start_time。

(2)第二排序,按照存取优先级。

(3)第三排序:钥匙的序号大小。

接下来,遍历结构体进行存取即可啦!

第一种思路代码如下:

#include<bits/stdc++.h>
using namespace std;
#define MAX 2010
int key[MAX];
int N;
struct Teachers
{
    int w,s,c;
    bool ans;
} Teacher[MAX]= {0,0,0,false};

void Store(vector<int> p)
{
    int a[MAX],t,num;
    num=0;
    for(int i=0; i<p.size(); i++)
    {
        t=p[i];
        a[i]=Teacher[t].w;
    }
    t=p.size();
    sort(a,a+t);//按照钥匙编号还
    for(int i=0; i<N; i++)
    {
        if(key[i]==0)
        {
            key[i]=a[num];
            num++;
        }
        if(num==t) break;
    }
}

void Get(vector<int> q)
{
    for(int i=0;i<q.size();i++)
    {
        int t=q[i];
        int num=Teacher[t].w;
        for(int i=0;i<N;i++)
        {
            if(key[i]==num)
            {
                key[i]=0;
                break;
            }
        }
    }
}

int main()
{
    vector<int>p,q;
    int K;
    int s_count=0;
    int start_time;
    scanf("%d%d",&N,&K);
    for(int i=0; i<N; i++) key[i]=i+1;
    for(int i=0; i<K; i++)
    {
        scanf("%d%d%d",&Teacher[i].w,&Teacher[i].s,&Teacher[i].c);
        if(i==0)
        {
            start_time=Teacher[0].s;
            continue;
        }
        if(start_time>Teacher[i].s) start_time=Teacher[i].s;
    }
    do{
        p.clear();//p用来存储取操作对应的结构体编号
        q.clear();//q用来存储存操作对应的结构体编号
        for(int i=0; i<K; i++)
        {
            if(!Teacher[i].ans&&Teacher[i].s==start_time)
            {
                p.push_back(i);
                Teacher[i].ans=true;
            }
            else if((Teacher[i].s+Teacher[i].c==start_time)&&Teacher[i].ans)
            {
                q.push_back(i);
                s_count++;
            }
        }
        start_time++;
        if(!q.empty()) Store(q);
        if(!p.empty()) Get(p);
    }while(s_count!=K);//钥匙全部还完即可
    for(int i=0; i<N; i++) printf("%d ",key[i]);
    return 0;
}

第二组思路的代码如下:

#include<bits/stdc++.h>
using namespace std;
#define MAX 2010//注意MAX的取值,不可以取2000一下。
int key[MAX];
struct Teacher
{
    int w;
    int cost;
    int ans;
}teacher[MAX];

bool cmp(struct Teacher a,struct Teacher b)
{
    if(a.cost!=b.cost) return a.cost<b.cost;
    else if(a.ans!=b.ans) return a.ans>b.ans;
    else return a.w<b.w;
}


int main()
{
    int N,K;
    scanf("%d%d",&N,&K);
    for(int i=0;i<N;i++) key[i]=i+1;
    K=K*2;
    for(int i=0;i<K;i++)
    {
        int w,s,c;
        scanf("%d%d%d",&w,&s,&c);
        teacher[i].w=w;
        teacher[i].cost=s;
        teacher[i].ans=0;
        i++;
        teacher[i].w=w;
        teacher[i].cost=s+c;
        teacher[i].ans=1;
    }
    sort(teacher,teacher+K,cmp);
    for(int i=0;i<K;i++)
    {
        if(!teacher[i].ans)
        {
            for(int j=0;j<N;j++) if(key[j]==teacher[i].w) key[j]=0;
        }
        else
        {
            for(int j=0;j<N;j++)
            if(key[j]==0)
            {
                key[j]=teacher[i].w;
                break;
            }
        }
    }
    for(int i=0;i<N;i++) printf("%d ",key[i]);
    return 0;
}

2.201709-3

问题描述
  JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,可以用来描述半结构化的数据。JSON 格式中的基本单元是值 (value),出于简化的目的本题只涉及 2 种类型的值:
  * 字符串 (string):字符串是由双引号 " 括起来的一组字符(可以为空)。如果字符串的内容中出现双引号 ",在双引号前面加反斜杠,也就是用 \" 表示;如果出现反斜杠 \,则用两个反斜杠 \\ 表示。反斜杠后面不能出现 " 和 \ 以外的字符。例如:""、"hello"、"\"\\"。
  * 对象 (object):对象是一组键值对的无序集合(可以为空)。键值对表示对象的属性,键是属性名,值是属性的内容。对象以左花括号 { 开始,右花括号 } 结束,键值对之间以逗号 , 分隔。一个键值对的键和值之间以冒号 : 分隔。键必须是字符串,同一个对象所有键值对的键必须两两都不相同;值可以是字符串,也可以是另一个对象。例如:{}、{"foo": "bar"}、{"Mon": "weekday", "Tue": "weekday", "Sun": "weekend"}。
  除了字符串内部的位置,其他位置都可以插入一个或多个空格使得 JSON 的呈现更加美观,也可以在一些地方换行,不会影响所表示的数据内容。例如,上面举例的最后一个 JSON 数据也可以写成如下形式。
  {
  "Mon": "weekday",
  "Tue": "weekday",
  "Sun": "weekend"
  }
  给出一个 JSON 格式描述的数据,以及若干查询,编程返回这些查询的结果。
输入格式
  第一行是两个正整数  n 和  m,分别表示 JSON 数据的行数和查询的个数。
  接下来  n 行,描述一个 JSON 数据,保证输入是一个合法的 JSON 对象。
  接下来  m 行,每行描述一个查询。给出要查询的属性名,要求返回对应属性的内容。需要支持多层查询,各层的属性名之间用小数点 . 连接。保证查询的格式都是合法的。
输出格式
  对于输入的每一个查询,按顺序输出查询结果,每个结果占一行。
  如果查询结果是一个字符串,则输出 STRING <string>,其中 <string> 是字符串的值,中间用一个空格分隔。
  如果查询结果是一个对象,则输出 OBJECT,不需要输出对象的内容。
  如果查询结果不存在,则输出 NOTEXIST。
样例输入
10 5
{
"firstName": "John",
"lastName": "Smith",
"address": {
"streetAddress": "2ndStreet",
"city": "NewYork",
"state": "NY"
},
"esc\\aped": "\"hello\""
}
firstName
address
address.city
address.postal
esc\aped
样例输出
STRING John
OBJECT
STRING NewYork
NOTEXIST
STRING "hello"
评测用例规模与约定
   n ≤ 100,每行不超过 80 个字符。
   m ≤ 100,每个查询的长度不超过 80 个字符。
  字符串中的字符均为 ASCII 码 33-126 的可打印字符,不会出现空格。所有字符串都不是空串。
  所有作为键的字符串不会包含小数点 .。查询时键的大小写敏感。
  50%的评测用例输入的对象只有 1 层结构,80%的评测用例输入的对象结构层数不超过 2 层。举例来说,{"a": "b"} 是一层结构的对象,{"a": {"b": "c"}} 是二层结构的对象,以此类推。

考点:

字符串的处理

易错:

字符串读取函数,如getchar(),cin.get(),getline(cin,s),等等

思路:

      首先我们要理清题目的意思。题中告诉大家两种格式,一种是字符串,一种是对象,对象中还可以有嵌套的存在。看过样例后,我们首先要解决的问题是,如何存储这些字符呢?当然,首选string类型变量存储,因为string类型操作多而且简便啊!

      再看看题目的输入输出描述,输入一个字符串要求输出对应字符串,问题来可,这要怎么办?熟悉STL的同学们肯定反应很快,用map<string,string>容器不就可以了吗?那么现在我们唯一的问题就是,如何存储对应字符串到map容器中了。

我们同样还是按照特殊到一般的思路,请看下面样例:

3 1
{
"firstName": "John"
}

firstName

      我们逐行读取,对于第一个'{',我想大部分读者开始的想法是用getchar()吸收这个字符,是的,楼主一开是也是这么想的,吸收过后n必须减1。但是大家仔细读题后可以发现,输入的对象的格式可以很随意,如上述式子也可以表示成:

{"firstName": "John"}

firstName

       那么这样一来,吸收掉'{'字符之后,到底应该读取n行,还是n--读取n行呢?吸收掉‘{’字符后很难界定,所以我们选择不吸收,直接处理即可,那么怎么处理呢?请继续向下看~。

      回归正题,当我们读取到"firstName": "John",对这个字符串怎么处理。我们可以把它分为两快,一块是键,另一块是值,我们需要一个变量来区分这两者,那么就设置一个bool变量吧。bool变量何时为true何时为false呢?回到"firstName": "John"本身,但我们测试到“““字符时我们开始接收字符,读取到另一个“““时,停止接受。读取到“:“时,这时候读取的值就为value啦。重复上述"""操作即可把字符接受完毕。分析到这里我们发现规律了:

(1)双引号为接收字符,停止接收字符的标志。

(2)“:”冒号为bool值改变的标志。

       这仅仅只是一个需要读取的样例,当{}中的样例变多的时候,我们还要处理"{","}"以及","。接下来就看题目给出的样例我们要怎么分析。如下:

10 5
{
"firstName": "John",
"lastName": "Smith",
"address": {
"streetAddress": "2ndStreet",
"city": "NewYork",
"state": "NY"
},
"esc\\aped": "\"hello\""
}
firstName
address
address.city
address.postal

esc\aped

   我们假设key存储前面的键值,value读取每一行,我们还有一个string a接收".."内的变量

    第一行的"{"我们先不管,先看下面一行。第一行有“”、:、,的存在,不难发现,有:时,为读取value,而有逗号的时候读取键,而当键赋值完毕后,读取的value刚好可以存在map容器中,而且,读取完毕之后,是不是有些string变量应该要清空呢?这是第一行我们看出来的规律。我们着重分析第二行。 

       第二行多了一个'{'字符,这个字符预示着接下来读取的都是嵌套的变量,键的值不能被清空。

       第三行,读取完前面部分后,赋值的时候,键值还要多加一个‘.’,这个要怎么和一般的键值写入区分呢?我们上面提到过,读取完一般字符串行,我们都会要清空键对应的存储变量,以及用来读取"...."内的字符串,而对于对象来说,键是不清空的,那么我们刚好可以依据这个来判断是否需要加上‘.’。在这一行里面,读取完后面的value,我们注意到必须清空key的一部分,便于下次赋值给Key,如key=address.streetAddress后,key的值要回到key=address的状态才方便读取下一行,给key赋值,那么自然想到赋值map容器后,key的值要处理一下。如果其中有点的话,调用substr函数即可,如果没有,说明上一次读取的是字符串,清空Key即可。

      接下来,还有一个重点字符"}",读取到它时,意味着一层嵌套要结束了,是的,那么这时候要怎么处理bool值和key值呢?我们在之前说过,当遇到','时,我们的bool值就要改变,而'}'后面要不有',',要不就是结束了,所以bool值在这里不改变也是可行的,问题在于key要如何改变呢?

       分情况讨论一下,如果是一层嵌套,我们只要清空key就可以了,可如果有两层嵌套呢?key值还必须保留一层嵌套,两者的区别在哪里?对,一层嵌套中key没有'.',而两层嵌套中有,那么我们在可以在设置一个for循环判断一下,是否需要截取,如果不需要,则清空,需要,就截取。

至此,我们可以开始写代码啦!

扩展:

C++ STRING类型:

(1)定义:string+变量名 如:string a;

(2)基本运算

string a,b,c;

c=a+b;//c字符串的值为a b字符串合并后形成的字符。

(3)基本函数

string a;

a=a.substr(0,i);//截取字符串a中0-(i-1)的字符,返回这些字符组成的字符串。

int b=a.size()//返回字符串a中的字符串个数

Getline()函数

常规用法:

string s;

getline(cin,s)//读取一行字符存入s中

(4)map容器。

定义:

map<type,type>name 如:map<int,int>a

赋值:直接赋值即可。

如:a[1]=2,

a["Sun"]=7;

满分代码如下:

#include<bits/stdc++.h>
#include<string>
using namespace std;

map<string,string>json;
bool ans;
string key;
string value;

void storage()
{
    //if(value[0]=='{') return; 形式错误
    for(int i=0; i<value.size(); i++)
    {
        if(value[i]=='{')
        {
            json[key]="OBJECT";
            ans=true;
        }
        else if(value[i]==':') ans=false;
        else if(value[i]==',') ans=true;
        else if(value[i]=='}')
        {
            int j;
            for(j=key.size()-1; j>=0; j--)
                if(key[j]=='.') break;
            if(j>0) key=key.substr(0,j);
            else key="";
        }
        else if(value[i]=='"')
        {
            string a;
            for(i=i+1; i<value.size(); i++)
            {
                if(value[i]=='\\')
                {
                    i++;
                    a+=value[i];
                }
                else if(value[i]=='"') break;
                else a+=value[i];
            }
            if(ans)
            {
                if(key=="") key=a;
                else key+='.'+a;
            }
            else
            {
                int j;
                json[key]="STRING "+a;
                for(j=key.size()-1; j>=0; j--)
                    if(key[j]=='.') break;
                if(j>0) key=key.substr(0,j);
                else key="";
            }
        }
        else continue;
    }
}

int main()
{
    int m,n;
    char a;
    cin>>n>>m;
    cin.get();//吸收换行符
    ans=true;
   while(n--)
    {
        getline(cin,value);
        storage();
    }
    while(m--)
    {
        string order;
        cin>>order;
        cout << (json[order] == "" ? "NOTEXIST" : json[order]) << endl;
    }
    return 0;
}

3.201709-3

某国的军队由 N个部门组成,为了提高安全性,部门之间建立了 M条通路,每条通路只能单向传递信息,即一条从部门 a到部门 b的通路只能由 ab传递信息。信息可以通过中转的方式进行传递,即如果 a能将信息传递到 bb又能将信息传递到 c,则 a能将信息传递到 c。一条信息可能通过多次中转最终到达目的地。
  由于保密工作做得很好,并不是所有部门之间都互相知道彼此的存在。只有当两个部门之间可以直接或间接传递信息时,他们才彼此知道对方的存在。部门之间不会把自己知道哪些部门告诉其他部门。

  上图中给了一个4个部门的例子,图中的单向边表示通路。部门1可以将消息发送给所有部门,部门4可以接收所有部门的消息,所以部门1和部门4知道所有其他部门的存在。部门2和部门3之间没有任何方式可以发送消息,所以部门2和部门3互相不知道彼此的存在。
  现在请问,有多少个部门知道所有 N个部门的存在。或者说,有多少个部门所知道的部门数量(包括自己)正好是 N
输入格式
  输入的第一行包含两个整数 N, M,分别表示部门的数量和单向通路的数量。所有部门从1到 N标号。
  接下来 M行,每行两个整数 a, b,表示部门 a到部门 b有一条单向通路。
输出格式
  输出一行,包含一个整数,表示答案。
样例输入
4 4
1 2
1 3
2 4
3 4
样例输出
2
样例说明
  部门1和部门4知道所有其他部门的存在。
评测用例规模与约定
  对于30%的评测用例,1 ≤ N ≤ 10,1 ≤ M ≤ 20;
  对于60%的评测用例,1 ≤ N ≤ 100,1 ≤ M ≤ 1000;
  对于100%的评测用例,1 ≤ N ≤ 1000,1 ≤ M ≤ 10000。

考点:DFS暴力搜图

易错:图的存储结构。

思路点拨:

       首先理清题意。我们能想到的第一个做法就是找到图中能到所有点的那些点,以及找到图中能被所有点到的那些点,怎么做呢?暴力搜图。

     常规的矩阵存储以及DFS遍历肯定是会超时的,所以对于图论的题目,推荐大家直接用二维的vector容器存储,既方便,又快捷。

下面说一下细节:

      常规的DFS遍历中的bool visited[MAX]数组肯定是要保留的,每次访问到一个点,那么对应两个点肯定就知道了彼此,我们画图可以发现,只设置一个一维数组来计数好像会产生重复的情况。一维数组不行,我们设置一个二维数组,如果两个点知道彼此,设置这个二维数组的值为1,否则为0,这样就不会重复了。

拓展:
DFS深度优先遍历:

(1)设置bool visited[MAX]。

(2)邻接点函数求出其邻接点。

满分代码如下:

#include<bits/stdc++.h>
using namespace std;
#define MAX 1010
int c_count[MAX][MAX];
bool visited[MAX];
int N,M;
vector<int>p[MAX];

void DFS(int w,int v)
{
    visited[w]=true;
    c_count[w][v]=c_count[v][w]=1;
    for(int i=0;i<p[w].size();i++)
    {
        if(!visited[p[w][i]]) DFS(p[w][i],v);
    }
}


int main()
{
    bool j_ans;
    int result=0;
    scanf("%d%d",&N,&M);
    memset(c_count,0,sizeof(c_count));
    while(M--)
    {
        int w,v;
        scanf("%d%d",&w,&v);
        p[w].push_back(v);
    }
    for(int i=1;i<=N;i++)
    {
        memset(visited,false,sizeof(visited));
        DFS(i,i);
    }
    int i,j;
    for(i=1; i<=N; i++)
    {
        for(j=1; j<=N; j++)
        {
            if(i==j) continue;
            if(c_count[i][j]==0) break;
        }
        if(j>N) result++;
    }
    printf("%d\n",result);
    return 0;

}





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值