出题人の锐评:
这场比赛各题通过率以及榜单,属实是出题人没想到的
emmmmmmm,知道诸君有怨,但先别急着骂
毕竟,这场的题总得来说,还是比较简单的对叭
按照出题人预估, I题、D题属于签到题,B题与A题难度在中等偏下,E题、F题与J题属中等偏上(需要一些小技巧或一点点的思维,并且在严谨逻辑的加持下才能完美通过)剩下的C、G、H题属难题(需要对某个知识点的深入理解 / 一些突发灵感 / 多次不同方向的尝试才有机会AC)
但在实际比赛中,榜确实有点点歪,本该作为签到题的D题可能由于题面过长或过于简单导致大家不敢提交,而 I 题的低通过率也许是同学们平时很少对转义字符这个知识点进行关注。同时,与 I 题相比,A题和B题得到了较高的通过率,或许是出题人低估了同学们的水平?
另外,为了让同学们在比赛中获得意想不到的乐趣,本次所有赛题都增设了或长或短的题目背景。(据参赛人反馈,读起题来就像看小说一样欸~)
在这里再次感谢 Liji 、Belinra 、Y、Lwh、Tmy、 关云长(关羽)、司马懿、马幼常(马谡)、胡桃、刻晴、百度百科 等实名参与题面构成,为本次比赛增添了无限乐趣。(嗯?为什么我听到有些同学说比赛期间压根没有见到这些人的名字?)
以下为本场比赛题目详解:
I.Really Understand?
题目类型:基础题
考察知识点:转义字符输出
在C语言中,有一些字符或字符组合有着特殊含义(转变了它原来的含义),被称为转义字符,这类字符往往无法被直接输出(或者说直接输出时无法与其他字符一样直接显示),而会以其他的形式显现。
最常见的转义字符即 '\n' ,当我们尝试输出 '\n' 时,会发现程序运行框里面什么都没有,只是比什么都不输出的程序多了一行空行
那对于像 '\n' 这种转义字符,我们想要输出它该怎么办呢?
很简单,只需要在其前面加上一个 '\'
像这样:
像 '\n' 一样的转义字符还有很多,本题要求输出的 " 就是其中一个,至于其他的转义字符就要同xio们自己去了解咯
AC代码:
#include <stdio.h>
int main()
{
printf("\"我当然懂с语言.\"");
return 0;
}
D.Will you reread topic?
考察知识点:对多个认识的中国字、词组成的句子的理解,以及做题的自信
我们先来看第一个知识点:对多个认识的中国字、词组成的句子的理解
很明显,雨水这个词大家都认识,岩浆这两个字大家也都认识,而下浆之后一共容纳多少单位的雨水,仿佛就有一种特殊的魔力,明明全是认识的字,连在一起无论如何都理解不了。对于这种情况,来自出题人善意的建议已放在了提示中:Reread the topic carefully。
第二个知识点:做题的自信
自信很重要,因为即使是错了,我们也曾尝试过,在错之后可以调整思维角度,寻找正确的思路,或经过调试使自己的代码AC,但如果一直怀疑自己是不是读错题了,或猜测出题人在这里挖了坑的话,出题人只能说:一直被误读,从未被驯服。(我错了,别打我)
所以,下浆之后可容纳的雨水自然是0,输出保留两位小数的话,就是0.00
代码实现如下:
#include <stdio.h>
int main()
{
printf("0.00");
return 0;
}
B. God of troops
考察知识点:自信找规律 / 思维证明
对于这种博弈问题,通常有两种做法:
第一种:自信找规律
简单来说,就是我们去从头列举几个数字(一般为连续数字,如1、2、3、4……),并判断当 n 分别等于这几个数字时它们的结果,从中寻找规律,并自信提交。
比如本题,列举之后可以发现,当 n = 1时,关二爷胜,当 n = 2时,Belinra胜,当 n = 3时,Belinra胜,当 n = 4时,Belinra胜,当 n = 5时,Belinra胜,当 n = 6时,Belinra胜,当 n = 7时,Belinra胜……
于是,我们可以大胆猜测,除 n = 1之外,全部为Belinra胜。
第二种:思维证明
如何证明呢?
对于本题,由于二爷先手,所以当 n 等于1时,二爷操作后堆中剩余0颗石子,Belinra无法进行操作,于是二爷胜。当 n 等于2时,二爷操作后堆中剩余1颗石子,此时Belinra拿走一颗石子即可取得胜利。当 n 等于3时,二爷操作后堆中剩余2颗石子,此时Belinra拿走两颗石子即可取得胜利。当 n 大于等于4时,Belinra只需要保证在每次操作后堆中剩余石子数量大于1颗或等于0颗(二爷无法一次拿完),如此Belinra即可保证自己必胜,又因为Belinra每次操作可以在拿走1颗和拿走2颗之间任选,故Belinra可以完全掌控对局。
如此可知,只要二爷第一次操作后,堆中石子数量不为零,则Belinra必胜。
代码实现如下:
#include <stdio.h>
int main()
{
long long n;
scanf("%lld",&n);
if(n == 1) printf("诸葛村夫,观尔插标卖首!");
else printf("千载谁堪伯仲间");
return 0;
}
质疑:威震华夏的汉寿亭侯武圣关云长,真的绝顶聪明吗?
A.Paradox
考察知识点:题面分析能力及数据处理能力
对于 t 组调查数据,我们只需分别记录选项一积累的票数与选项二积累的票数,将最终票数进行比较即可。
代码实现如下:
#include <stdio.h>
int main()
{
int t, k, x, y;
scanf("%d", &t);
int sum1 = 0, sum2 = 0; //sum1记录选项一的最终票数,sum2记录选项二的最终票数
while (t --)
{
scanf("%d %d %d", &k, &x, &y);
if (k == 1)
{
sum1 += x;
sum1 -= y;
}
else
{
sum2 += x;
sum2 -= y;
}
}
if(sum1 >= sum2) printf("safe");
else printf("dangerous");
return 0;
}
E.Son or True Son
考察知识点:对标记数组的使用(数组的灵活使用)
对于每一个给定的集合B,要判断它是否是集合A的(真)子集,需要判断集合B中的每个元素是否均在集合A中出现过。
如果用常规的遍历整个集合A寻找集合B中的某个元素是否出现过,由集合的互异性可知,对于一个有m个元素的集合B,我们需要遍历m次集合A,若A有n个元素,则循环总次数约为m * n次。
结合题面给的数据范围,我们可以估算程序最多的循环次数:t * n * m == 100 * 10000 * 30000
这个循环次数很大,大到计算机一秒内可能无法结束程序的运行,而题面的时间限制是1Sec(1秒)。所以我们需要去寻找一个比较便捷的方法解决这道题。(不要试图解决出题人)
对于每一个集合B中的元素,如果我们不去遍历集合A,如何知道它是否在集合A中出现过呢?
夜里,我横竖睡不着仔细想了半夜,才从题面中想出题解来,满题解都写着两个字:标记!
我们把在集合A中出现过的元素都打上标记,然后对每个集合B中的元素,我们只要看这个元素有没有被标记过就好了!
说干就干!题面说元素的数据范围在1e6以内,那我们就开一个足够大的st数组,将集合元素作为数组下标,当前下表在数组中对应的元素用0 / 1来区分,0代表没有标记,1 代表已被标记。
这样,我们程序的时间复杂度就被大大降低了
理论成立,代码实现如下:
#include <stdio.h>
int n, m, st[1000010];
int main()
{
int t, x;
scanf("%d",&n);
for(int i = 1; i <= n; i ++)
{
scanf("%d", &x);
st[x] = 1;
}
scanf("%d",&t);
while(t --)
{
scanf("%d",&m);
int f = 1, cnt = 0;
for(int i = 1; i <= m; i ++)
{
scanf("%d",&x);
if(st[x])
{
cnt ++;
}
else f = 0;
}
if(f)
{
if(cnt == n) puts("false");
else puts("true");
}
else puts("6");
}
return 0;
}
F.Lucky Number For You
考察知识点:对二进制的理解
对于二进制形式下只包含 0 的数数字,只有 0 一个
而对于二进制形式下只包含 1 的数字,有 1、3、7、15、31等
以上两种数字都有一个特点:它们加 1 之后,恰是 2 的整数次幂
比如:0 加 1 之后为 2 的 0 次方,1 加 1 之后为 2 的 1 次方,3 加 1 之后为 2 的 2 次方 ……
而对于幸运数字的个数:
因为我们求的幸运数字均为整数,故我们可以将闭区间[0,n]等价替换为左闭右开区间[0,n + 1)
#include <stdio.h>
long long n;
int main()
{
int ans = 1;
scanf("%lld", &n);
n += 1;
while(n / 2)
{
ans ++;
n /= 2;
}
printf("%d\n", ans);
return 0;
}