中国石油大学 2018-2019赛季多校联合新生训练赛第一场 题解与补题

这场比赛是在18年12月,因为当时完全不敢用C++,感觉很遥远的样子…代码都是拿C实现的,许多地方其实也可以优化的。

问题 A: 录取分数线

时间限制: 1 Sec 内存限制: 128 MB
题目描述
新学年,学校将成立信息学兴趣小组提高班。由于指导教师精力有限,只能以选拔考试的成绩为依据,按从高到低的分数,从N个参加选拔的学生中录取不超过M个成员。录取的成员要尽可能地多,但不得超过M个(含M个)。由于可能会有并列分数出现,为了保证公平,有时只得忍痛割爱,可能录取的成员会达不到计划数M。请你编程划定录取分数线。

输入
有N+1行,第一行是报名人数N和录取人数M。以下N行是考试成绩,已按从高到低的顺序排列。N、M和成绩均是1000以内的正整数,N≥M。数据保证不会所有的成绩都相同。

输出
只有1行,为录取分数线。

样例输入
10 5
99
98
97
96
95
94
93
92
91
90

样例输出
95

题目分析
第一题送福利,只判断第m+1位是否与第m位相等即可。若不相等则为第m位成绩,相等则从m往回找找到第一个大于m+1位成绩的学生的成绩即可。

#include<stdio.h>
#define N 100000
int main(void)
{
    int a[N],n,m,i,j,t;
    scanf("%d %d",&n,&m);
    for(i=0;i<n;i++)
        scanf("%d",&a[i]);
    for(i=0;i<n;i++)//其实这段排序可以删掉我没注意题目中已经有序
        for(j=0;j<n-i;j++)
            if(a[j]<a[j+1])
            {
                t=a[j];
                a[j]=a[j+1];
                a[j+1]=t;
            }
    i=m-1;
    if(a[i]==a[m])
    {
        while(a[i]==a[m-1]) i--;
    }
    printf("%d",a[i]);
    return 0;
}

问题 B: 电子警察

时间限制: 1 Sec 内存限制: 128 MB
题目描述
现在很多地方的道路路口都安装了电子警察,即交通违章自动拍照系统。这些系统一般在路口的地下埋设感应线圈,通过传感器判断汽车是否在红灯时通过路面,来控制数码相机自动拍照。在安装这种系统需要挖掘地面,施工麻烦,成本又高。于是有人研究出了同摄像机自动识别车牌并判断违章行为的系统,这样一来,电子警察安装就方便多了,成本也大大降低。请你编程实现其中的一个功能,给出一批某一时间识别后的车牌号码及行进方向,判断该车是否违章,并记录下来。违章的规则设定为:先设置左转、直行、右转依次绿灯通行时间(以秒为单位,只允许一个方向绿灯),先左转绿灯,然后直行绿灯,最后右转绿灯,在其中一个绿灯时,其余两盏灯为红灯状态,假设时间生效在零时整,且给出的数据只限定当天。闯红灯为违章。

输入
第1行有4个整数,以一个空格隔开,依次为左转、直行、右转通行的绿灯持续秒数和识别的车辆数N(1≤N≤10000),后面的N行,表示每辆车的信息,格式为“时间+方向+车牌”,其中时间为6位数字,方向为1个字母(L表示左转,S表示直行,R表示右转),车牌为8个字符,之间没有空格。如081528LZJBB0001,表示车牌号为ZJBB0001的车辆在8时15分28秒左转。

输出
违章车辆的车牌号码,每辆车一行,不含空格,按输进去的先后顺序输出。

样例输入
15 30 20 3
000046SZJBB8888
030950LJSAA9999
201509RBJC7777D

样例输出
ZJBB8888
BJC7777D

题目分析
这道题我吃了10次罚时…主要还是细节问题,比如最后一秒取余以后会变为0,此时要把0当做右转处理,以及对于输入行末空格的处理等等…现在想想用cin或者scanf读入就没这回事了
判断条件解释:取余后左转的时间为(0,l],直行时间为(l,s],右转时间为(s,r)和0,只要转向与时间不符则为闯红灯。

#include<stdio.h>
#include<string.h>
#define N 20
int main(void)
{
    char a[N];
    int n,i,l,s,r,c,j,d;
    scanf("%d %d %d %d",&l,&s,&r,&n);
    while(getchar()!='\n');//处理行末空格,不加while会WA
    s=l+s;//s此时代表直行结束的时刻
    r=s+r;//r此时代表右转结束的时刻,即一个周期的时长
    while(n-->0)
    {
        gets(a);
        c=0;//c代表从计时开始共经历的时间长度对总时长取余后的结果
        for(j=0;j<3;j++)
        {
            d=0;
            for(i=j*2;i<j*2+2;i++)
                d=d*10+a[i]-'0';
            c=c*60+d;
            c=c%r;
        }
        if((a[6]=='L'&&(c==0||c>l))||(a[6]=='S'&&(c<=l||c>s))||(a[6]=='R'&&c<=s&&c!=0))
            puts(a+7);
    }
    return 0;
}

问题 C: 查找特定的合数

时间限制: 1 Sec 内存限制: 128 MB
题目描述
自然数中除了能被1和本身整除外,还能被其他数整除的数叫合数。每个合数都可以写成几个质数相乘的形式,这几个质数都叫做这个合数的质因数。比如8=2×2×2,2就是8的质因数。在1—N(N≤200000)按从小到大顺序排列的自然数序列中,查找第M个有X(2≤X≤6)个不同质因数的合数。例如,第3个有2个不同质因数的合数是12(12只有2、3两个不同的质因数,在12之前有2个不同质因数的合数分别为6和10)。

输入
共1行,分别为M,X。

输出
共1行,为第M个有X个不同质因数的合数。

样例输入
3 2

样例输出
12

解题思路
这道题思路很简单,从2开始进行枚举,对每一个数i,从2到i枚举j,如果j是i的因子则让i除以j,直到i没有j这个因子为止,并令计数器+1,如果i中含有的因子数量(计数器)和x相等就让m-1,当m减到0时就意味着当前枚举的数是第m个符合条件的数,于是就有了我最初的代码:

#include<stdio.h>
#include<stdlib.h>
int main(void)
{
    int m,x,i,k,j,c=0;
    scanf("%d %d",&m,&x);
    for(i=2;m>0&&i<200000;i++)
    {
        k=i;//防止因为循环内的计算改变i的值
        c=0;
        for(j=2;j*2<=i&&j<=k;j++)
        {
            if(k%j==0)//找到质因数
            {
                while(k%j==0)
                    k=k/j;
                c++;
                if(c>x) break;//因为c只增不减,当c>x,c就不可能等于x了
            }
        }
        if(c==x) m--;//满足条件m--
    }
    if(i!=200000) printf("%d",i-1);
    return 0;
}

然而我等到的不是AC,而是TLE(时间超限)
再进行分析发现,除去的只会是质因子,因为j枚举到合数时一定枚举过这个合数的质因子,而i此时一定不含这些质因子,也一定不含这个合数因子。所以我们只需要枚举质数,这样就可以省下不必要的枚举合数的时间。而质数的判断也很简单:内层for执行完毕后如果c=0则i就是质数(小于i的因子个数为0)
就可以将代码进一步优化:

#include<stdio.h>
#include<stdlib.h>
#define N 200000
int main(void)
{
    int a[N]={2};//质数数组,从2开始边查找边添加数据
    int m,x,i,k,j,c=0,l=1;//l为当前数组a内质数的个数
    scanf("%d %d",&m,&x);
    for(i=3;m>0&&i<200000;i++)
    {
        k=i;
        c=0;
        for(j=0;j<l&&a[j]*2<=i&&a[j]<=k;j++)//只枚举数组a内的数,即只枚举质数
        {
            if(k%a[j]==0)
            {
                while(k%a[j]==0)
                    k=k/a[j];
                c++;
                if(c>x) break;
            }
        }
        if(c==x) m--;
        if(c==0) a[l++]=i;//向a内添加质数,同时令质数个数+1
    }
    if(i!=200000) printf("%d",i-1);
    return 0;
}

于是我使用这个自己现场编的方法节省了运行时间,AC了这一道题。

问题 D: 传话游戏

时间限制: 1 Sec 内存限制: 128 MB
题目描述
有这样一个朋友网络,如果a认识b,那么a收到某个消息,就会把这个消息传给b,以及所有a认识的人。但是,请你注意,如果a认识b,b不一定认识a。现在我们把所有人从1到n编号,给出所有“认识”关系,问如果i发布一条新消息,那么会不会经过若干次传话后,这个消息传回给了i(1≤i≤n)。

输入
第1行是两个数n(n<1000)和m(m<10000),两数之间有一个空格,表示人数和认识关系数。接下来的m行,每行两个数a和b,表示a认识b(1≤a,b≤n)。认识关系可能会重复给出,但1行的两个数不会相同。

输出
一共有n行,每行一个字符T或F。第i行如果是T,表示i发出一条新消息会传回给i;如果是F,表示i发出一条新消息不会传回给i。

样例输入
4 6
1 2
2 3
4 1
3 1
1 3
2 3

样例输出
T
T
T
F

分析
floyd裸题…但是当时不会啊…确实学到了新东西。

#include<stdio.h>
#include<stdlib.h>
int main(void)
{
    int m,i,j,x,y,n,k;
    int *a;
    scanf("%d %d",&n,&m);
    n++;
    a=(int*)calloc(n*n,sizeof(int));//因为出过多次RE,把数组换成了申请内存
    while(m-->0)
    {
        scanf("%d %d",&x,&y);
        a[x*n+y]=1;//二维数组,类似a[x][y]=1,但是由于我用了指针,不能直接用二维数组,采取了乘n的方法
    }
    for(k=1;k<n;k++)
        for(i=1; i<n; i++)
            for(j=1; j<n; j++)
                if(a[i*n+k]==1&&a[k*n+j]==1)
                {
                    a[i*n+j]=1;
                }
    for(i=1; i<n; i++)
    {
        if(a[i*n+i]==1)
            printf("T\n");
        else
            printf("F\n");
    }
    return 0;
}

问题 E: 谁是冠军

时间限制: 1 Sec 内存限制: 128 MB
题目描述
小Q自从参加某小学计算机兴趣小组以来,对编程产生了浓厚的兴趣。他发现用计算机编程不但可以训练思维,还可以解决学习和生活中的一些实际问题。比如,世界杯足球赛时,小Q就经常把其中的一些球队列出来,组成一个小团队,然后根据规则计算积分,并根据积分的高低看看这个团队内谁是冠军。假如某次足球赛的积分规则如下:每胜一局得3分,每平一局得1分,每输一局扣1分,积分最高者为冠军。小Q就想编这样一个程序,输入若干球队的成绩,就能自动求出这个团队中谁是冠军。你也能编一个吗?

输入
输入有两行,第一行是输入的球队数,第二行是每队的比赛成绩,依次为球队编号、胜局数、平局数、负局数(均为小于1000的整数),每个数据间用一空格隔开。输入的数据保证积分各不相同。

输出
只有一个数,就是冠军队的编号。

样例输入
4
1 5 4 3
2 3 4 5
3 6 3 3
4 4 2 6

样例输出
3

解题思路
输入时计算积分,如果积分高于目前最高值,更新积分与编号即可。注意输球扣1分。价值两发罚时

#include<stdio.h>
#define N 100000
int main(void)
{
    int n,a,b,c,d,id,s=-1001;
    scanf("%d",&n);
    while(n-->0)
    {
        scanf("%d %d %d %d",&d,&a,&b,&c);
        if(3*a+b-c>s)
        {
            s=3*a+b-c;
            id=d;
        }
    }
    printf("%d",id);
    return 0;
}

问题 F: 搭积木的诀窍

时间限制: 1 Sec 内存限制: 128 MB
题目描述
小Q的编程技术在一次搭积木比赛中也成了秘密武器。原来,比赛的规则是这样的:给你N个小木块(全部为一样大小的正方体),快速搭成如下图规则的形状(下图为5层的规模),要求层数为最大限度。由于小Q编了个程序,只要输入小木块个数N,就可以马上求出最多可以搭几层,还剩几个,所以小Q每次都是一次成功,从不需要翻工,速度也就领先了。你会编小Q这样的程序吗?
在这里插入图片描述
输入
只有一个整数N,表示小木块的个数,已知1≤N≤30000。

输出
有两行整数,第一行是最多可以堆的层数,第二行是剩余的小木块数。

样例输入
37

样例输出
5
2

分析
1层需要1块,1层与2层相差3,2层与3层相差6,3层与4层相差10……
发现1 3 6 10这个数列差值成等差数列(2 3 4),可利用这点计算两层间的差值,每次从积木块总个数减去差值,如果剩余值小于0则跳出循环并把刚刚减去的值加回去就可得到剩余木块数,而层数可通过减去的次数得到。

#include<stdio.h>
#define N 100000
int main(void)
{
    int n,a=0,i;
    scanf("%d",&n);
    n++;//这个处理是为了恰好拼完的情况,让n+1使恰好拼完时也会再多减一次,避免判断是否恰好拼完
    for(i=1;n>0;i++)
    {
        a=a+i;//a为两层间差值
        n=n-a;
    }
    n=n+a-1;//多减一次a加回来,n多加的1减掉
    i=i-2;//因为n多减了一层i多1,for内i++又多1,总共多2,故i-2
    printf("%d\n%d",i,n);
    return 0;
}

问题 G: 卡布列克常数

时间限制: 1 Sec 内存限制: 128 MB
题目描述

最近,小Q在数学兴趣课中了解了“卡布列克常数”。卡布列克是一位数学家,他在研究数字时发现:任意一个不是用完全相同数字组成的四位数,如果对它们的每位数字重新排序,组成一个最大的数和一个最小的数,然后用最大数减去最小数,差不够四位数时补零,类推下去,最后将变成一个固定的数:6174,这就是卡布列克常数。
例如:4321-1234=3087
8730-378=8352
8532-2358=6174
7641-1467=6174
……
小Q想,我能不能编程来验证呢?输入一个符合条件的四位数,然后验证运算过程。

输入
共1行,为任意一个不是用完全相同数字组成的四位数。

输出
变为卡布列克常数的运算过程,由若干行组成,每行是一个算式,不含空格。

样例输入
4321

样例输出
4321-1234=3087
8730-378=8352
8532-2358=6174

直接模拟,递归即可。

#include<stdio.h>
#define N 100000
void fun(int x)
{
    if(x==6174) return;//边界
    int a[4],i,j,b,c,d;
    //获取四位数字
    a[0]=x/1000;
    a[1]=x/100%10;
    a[2]=x/10%10;
    a[3]=x%10;
    //排序
    for(i=0;i<3;i++)
        for(j=0;j<3-i;j++)
            if(a[j]<a[j+1])
    		{
        		x=a[j];
        		a[j]=a[j+1];
        		a[j+1]=x;
    		}
    b=a[0]*1000+a[1]*100+a[2]*10+a[3];//最大值
    c=a[3]*1000+a[2]*100+a[1]*10+a[0];//最小值
    d=b-c;
    printf("%d-%d=%d\n",b,c,d);
    fun(d);//继续递归
}
int main(void)
{
    int n;
    scanf("%d",&n);
    fun(n);
    return 0;
}

问题 H: 扫雷游戏

时间限制: 1 Sec 内存限制: 128 MB
题目描述
小Q空的时候挺喜欢玩玩电脑游戏的。自从编程技术提高后,他就想,要是自己也能开发出一款游戏来,那该多好啊!不过,小Q也不着急,先练好基本功再说。Windows中就有一款叫扫雷的小游戏,挺好玩的,不过想编出一个来,还真不容易。小Q就自己设想了一种简单的扫雷游戏:在n行2列的方格棋盘上,左列某些方格内埋有地雷,而右列每个方格中都有一个数字(0~3),第I格的数字表示:左列第I-1、I、I+1格(即:上、中、下三格)中埋雷的总数。如下所示:左图是初始状态,右图是扫雷完成状态(插小旗的方格内有雷)。

你的任务是:根据右列的数字分析出左列格子中的地雷(0表示无雷,1表示有雷),并且统计出左列格子中地雷的总数。
小Q想,如果这样的任务能完成了,相信编出更复杂的扫雷游戏也就为期不远了。

输入
第一行,一个整数N(2≤N≤40),第二行有N个数字(以一个空格相隔),表示右列格子中的数字。输入数据保证正确有解。

输出
第一行是N个0、1数字(没有空格相隔),表示左列每格中有无地雷。第二行一个整数,表示地雷总数。

样例输入
7
1 2 3 2 2 2 2

样例输出
0111011
5

分析
典型搜索题,但是我当时不会搜索啊
于是我模拟了玩扫雷的思路:(认真脸.jpg)

  1. 如果一个格子是0,它对应的三个(或两个) 格子必然无雷;
  2. 如果一个格子是3,它对应的三个格子都是雷;
  3. 如果一个格子是2并且已知其中一个位置无雷(或者2在两端),剩余两个格子必然都是雷;
  4. 如果一个格子是1并且已知其中一个位置有雷,剩余两个格子(或1个)必然无雷;
  5. 如果一个格子是2并且已知其中两个位置有雷,剩余一个格子必然无雷;
  6. 如果一个格子是1并且已知其中两个位置无雷(或者1在两端且其中一个位置无雷),剩余一个格子必然是雷;

以上思路可以解决绝大多数情况了,但是还有没考虑到的地方。
这个思路的突破口在于0,3和两侧的2,但是如果有这样的测试数据:
5
1 2 1 2 1
则应该输出10101和3,但是由于这个数据不包含0 3和两侧的2,所以需要新的突破口:

  1. 有一个在两侧的格子为1且旁边的格子是1,则第三个格子无雷;
  2. 有一个在两侧的格子为1且旁边的格子是2,则第三个格子有雷;

即:
已知:12xxxx11
可推:xx1xx0xx
(x代表合法的其他数据)

同理可得:

  1. 有一个格子为1,它控制的三个格子中两侧的格子中有一个无雷,且旁边的格子是1,则第三个格子无雷;
  2. 有一个格子为1,它控制的三个格子中两侧的格子中有一个无雷,且旁边的格子是2,则第三个格子有雷;
  3. 有一个格子为2,它控制的三个格子中两侧的格子中有一个有雷,且旁边的格子是1,则第三个格子无雷;
  4. 有一个格子为2,它控制的三个格子中两侧的格子中有一个有雷,且旁边的格子是2,则第三个格子有雷;

已知:
xxx11xxx xxx21xxx xxx12xxx xxx22xxx
xxxxx0xx xxxxx0xx xxxxx1xx xxxxx1xx
分别可推:
xx0xx0xx xx1xx0xx xx0xx1xx xx1xx0xx

原因:以第一组为例,第五格的1表明4 5 6共有1个雷,由于6无雷,则4 5中一个有雷一个无雷,第四格的1表明3 4 5共有1个雷,由于4 5中一个有雷一个无雷,3必然无雷,否则3 4 5就有了两个雷。

那么可以看代码了:

#include<stdio.h>
#define N 100000
int fun(char b[N],int n)
{
	//函数功能:判断是否完成扫雷,完成返回0,未完成返回1
    int i;
    for(i=0;i<n;i++)
        if(b[i]=='2')
        return 1;
    return 0;
}
int main(void)
{
    char b[N];
    int i;
    int a[N],n,c=0;
    scanf("%d",&n);
    for(i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
            b[i]='2';//初始情况,默认每位都为2
        }
    for(i=0;i<n;i++)
    {
    	//将0和3判断(思路1、2)
        if(a[i]==3)
            b[i-1]=b[i]=b[i+1]='1';
        if(a[i]==0)
        {
            b[i]='0';
            if(i-1) b[i-1]='0';
            if(i+1<n) b[i+1]='0';
        }
    }
    while(fun(b,n))//当扫雷未完成时继续进行
    for(i=0;i<n;i++)
    {
        if(a[i]==2)
        {
        	//思路3
            if(i==0)
                {b[i]=b[i+1]='1';continue;}
            if(i==n-1)
                {b[i]=b[i-1]='1';continue;}
            if(b[i-1]=='0')
                {b[i]=b[i+1]='1';continue;}
            if(b[i]=='0')
                {b[i-1]=b[i+1]='1';continue;}
            if(b[i+1]=='0')
                {b[i]=b[i-1]='1';continue;}
            //思路5
            if(b[i-1]=='1'&&b[i+1]=='1')
                {b[i]='0';continue;}
            if(b[i]=='1'&&b[i+1]=='1')
                {b[i-1]='0';continue;}
            if(b[i-1]=='1'&&b[i]=='1')
                {b[i+1]='0';continue;}
            //思路11与12
            if(b[i-1]=='1')
            {
                if(a[i+1]==2&&n>2)
                    b[i+2]='1';
                if(a[i+1]==1&&n>2)
                    b[i+2]='0';
            }
            if(b[i+1]=='1')
            {
                if(a[i-1]==2&&n>2)
                    b[i-2]='1';
                if(a[i-1]==1&&n>2)
                    b[i-2]='0';
            }
        }
        if(a[i]==1)
        {
        	//思路6(两侧)
            if(i==0&&b[i+1]=='0')
                {b[i]='1';continue;}
            if(i==0&&b[i]=='0')
                {b[i+1]='1';continue;}
            if(i==n-1&&b[i]=='0')
                {b[i-1]='1';continue;}
            if(i==n-1&&b[i-1]=='0')
                {b[i+1]='1';continue;}
            //思路4
            if(b[i-1]=='1')
                {b[i]=b[i+1]='0';continue;}
            if(b[i]=='1')
                {b[i-1]=b[i+1]='0';continue;}
            if(b[i+1]=='1')
                {b[i]=b[i-1]='0';continue;}
            //思路6(中间)
            if(b[i-1]=='0'&&b[i+1]=='0')
                {b[i]='1';continue;}
            if(b[i]=='0'&&b[i+1]=='0')
                {b[i-1]='1';continue;}
            if(b[i-1]=='0'&&b[i]=='0')
                {b[i+1]='1';continue;}
            //思路7-10
            if(i==0||b[i-1]=='0')
            {
                if(a[i+1]==2&&n>2)
                    b[i+2]='1';
                if(a[i+1]==1&&n>2)
                    b[i+2]='0';
            }
            if(i==n-1||b[i+1]=='0')
            {
                if(a[i-1]==2&&n>2)
                    b[i-2]='1';
                if(a[i-1]==1&&n>2)
                    b[i-2]='0';
            }
        }
    }
    b[n]=0;//字符串结束符
    puts(b);
    for(i=0;i<n;i++)
        if(b[i]=='1') c++;
    printf("%d",c);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/kaixinqi/article/details/87105854