比赛信息
题目难度
正确率分布情况:
题目详解
解析:该题目是本应为简单的题目,可是因为许多同学的粗心还有对C的输入输出函数了解不够,导致该题目wa了无数次,正确率也大大降低。
出题人(kmjj)的博客对此有详细解释:如何理解c/c++中的输入/输出函数?—— 山岳库博
首先,该题目需要先用输入函数接收用户输入的字符串,这里要注意:
-
scanf 函数是不接收空格字符的。 -
gets 函数支持接收空格。
所以在第5个命令处,许多同学就是在这里wa了,题目要求输入的命令是
代码:
(1).
#include <stdio.h>
#include <string.h>
int main() {
//定义变量来存储输入的字符串
char command[20];
//输入字符串
scanf("%s", command);
//下面对字符串进行识别并输出对应答案
if (strcmp(command, "base") == 0) {
printf("严禁发布色情内容,严禁涉政");
} else if (strcmp(command, "ms") == 0) {
printf("萌新不推荐使用 vs");
} else if (strcmp(command, "water") == 0) {
printf("本群可以灌水但不要一直灌水");
} else if (strcmp(command, "train") == 0) {
printf("多读书多动手,编程能力是练出来的不是想出来的");
} else if (strcmp(command, "ask") == 0) {
printf("掌握提问艺术,从你我他做起");
} else if (strcmp(command, "oj") == 0) {
printf("oj 平台如果自己原来有一个账号的话用学校注册的账户继续往后写就行了,不需要把写过的题再交一遍。");
} else if (strcmp(command, "standard") == 0) {
printf("编写代码务必注意代码规范");
}
return 0;
}
(2).
#include <stdio.h>
#include <string.h>
int main() {
char input[20];
gets(input);
if (strcmp(input, "base") == 0)
printf("严禁发布色情内容,严禁涉政");
else if (strcmp(input, "attention ms") == 0)
printf("萌新不推荐使用 vs");
else if (strcmp(input, "water") == 0)
printf("本群可以灌水但不要一直灌水");
else if (strcmp(input, "train") == 0)
printf("多读书多动手,编程能力是练出来的不是想出来的");
else if (strcmp(input, "ask question") == 0)
printf("掌握提问艺术,从你我他做起")
else if (strcmp(input, "oj") == 0)
printf("oj 平台如果自己原来有一个账号的话用学校注册的账户继续往后写就行了,不需要把写过的题再交一遍。");
else
printf("编写代码务必注意代码规范");
return 0;
}
解析:本题目就是一个简单的公式代入题目,只需要将相应的参数依次输入到公式中,运算即可得出结果。但是这种计算题目,很多人很容易把变量的取值范围以及变量的类型忽略掉,最后导致题目wa了。
本题就很容易犯这样的错误,题目中给的公式里M和N题目描述是
本题目的第二个坑就是关于
出题人(kmjj)的博客对此有详细解释:如何理解c/c++中的输入/输出函数?—— 山岳库博
例如:
int p=2;
double res=5.2348;
printf("%.*lf",p,res);
在上面的代码中,printf里的格式化文本中出现了一个“
double res=5.2348;
printf("%.2lf",res);
那么我们就可以利用此特性来解决这一道题目了。代码如下:
#include <stdio.h>
#include <math.h>
int main() {
double M, N, e, RT;
int P;
e = 2.718281828459045;
// 依次输入 M、N 和 P
scanf("%lf %lf %d", &M, &N, &P);
// 计算红灯时长 RT
RT = M * pow(e, (-0.114514 * N));
// 输出结果
printf("%.*lf\n", P, RT);
return 0;
}
解析:本题目属于数字求和题目,但是比原始的求和要多了一步,那就是剔除数据中的小数。注意题目中的一句话:“
代码如下:
(1).法一:
#include <stdio.h>
#include <string.h>
int main() {
double num;
int sum = 0;
char input[100];
while (1) {
scanf("%s", input);
if (strcmp(input, "0") == 0) {
break; // 如果输入为0,则结束循环
}
//遍历字符串,检查是否存在小数点
int len = strlen(input);
int dotIndex = -1;
for (int i = 0; i < len; i++) {
if (input[i] == '.') {
dotIndex = i;
break;
}
}
//检查到存在小数点,那么此数为小数,跳过该数字
if (dotIndex != -1) {
continue;
}
//将字符串转换为实数,并将其加到总和中
sscanf(input, "%lf", &num);
sum += (int)num;
}
// 输出整数的总和
printf("%d\n", sum);
return 0;
}
(2).法二:
#include <stdio.h>
#include <stdbool.h>
int main() {
int n;
int sum = 0;
char p;
while (true) {
//输入整数部分
scanf("%d", &n);
//接收字符部分用来判断是否存在小数点
int c = scanf("%c", &p);
if (c != EOF && p == '.') {
scanf("%*d"); // 使用 %*d 跳过小数的整数部分
continue; // 跳过此数值,继续接收下一个数值
} else if (n == 0) break;
sum += n;
}
printf("%d", sum);
return 0;
}
解析:本题目考察对数据统计方法的使用以及逻辑运用。通读题目之后,观察数据特点,我们会发现,只有一个ID是用于统计的关键数据,时间戳以及Name都不太重要,只需要统计不同ID的个数即可得到结果。
代码如下:
(1).C语言
#include <stdio.h>
#include <string.h>
int main() {
//定义ID数组
int record[1001];
//初始化ID数组
memset(record, 0, sizeof(record));
int id;
int result = 0;
//接收数据,此处又出现了*的用法,~的作用相当于取反
while (~scanf("%*s %d %*s", &id)) {
/*
此处的思路比较新奇,灵活运用了自增运算的特性以及题目变量的取值范围
++var的运算顺序是先递增变量var再返回var的数值,
那么此处数组record初始值都为1,那么第一次递增之后就是1,
接下来如果出现了重复的id,那么就会在原来递增过的基础上继续递增,
此时,就不符合if判断中的条件了,那么也就不会计数。
此处id的取值范围是[100000000,100001000],拿着输入的id-100000000,就可以得出此id相应的位置。
差值的取值范围是[0,1000]
*/
if (++record[id - 100000000] == 1) ++result;
}
printf("%d", result);
return 0;
}
(2).C++(stl的运用)
#include <iostream>
#include <string>
#include <map>
int main() {
std::map<long long, std::string> users = {{0, "name"}}; //定义发言人map数组
int count = 0; //计数结果
long long id; //储存输入的id
std::string timestamp; //时间戳
std::string name; //名字
//输入时间戳 id 名字
while(std::cin >> timestamp >> id >> name)
{
//从map数组中查找此id是否已经存在
auto it = users.find(id);
//如果不存在,那就证明是新的发言人
if(it == users.end())
{
//我们需要将其计数
count++;
//并将其记录到数组中
users[id] = name;
}
}
std::cout << count;
return 0;
}
解析:此题目接近于阅读理解题目了,但是其中的逻辑关系是比较多的。我们需要注意到这句话:
“
那么第i张牌上每个数字的二进制第i位都是1,则必存在一个二进制数的前i位都是1,那么该数字也就对应着每张牌上的最大的数字。
再通过观察例子中三张牌的数字特点我们发现,它们都有数字7,而数字7的二进制为
代码如下:
代码中,
#include <stdio.h>
#include <limits.h>
int main() {
int input;
//输入卡牌总数量N
scanf("%d", &input);
//将 UINT_MAX 右移(32 - input)位
unsigned result = UINT_MAX >> (32 - input);
for (int i = 0; i != input; ++i) {
printf("%u\n", result);
}
return 0;
}
解析:这道题目充斥着对于二进制位运算的考察,如果没有比较坚实的二进制位运算基础,那么做起来是比较困难的,也可以做,但是在流程上会多出来很多代码。
首先我们从E题中可以看到:“
在此题目中,要求我们求指定卡牌上的所有数字,那么我们首先要缕一缕这个流程:
- 首先找出来这些卡牌上的最大数字
- 根据E题中的规律,确定每张卡牌上的最小数字
- 在最小数字与最大数字之间遍历,查找符合第i位为1的数字并依次输出
捋清楚思路之后,我们即可开始实现相应的代码:
代码如下:
- 1的二进制为
0001 ,例如我们的卡牌总数量是3,那么通过左移运算1 << 3 可以实现0001 ->1000 的变换。而7的二进制为0111 ,将其加1即可得到1000 ,1000 的十进制是8,8就是7的下一个数,7又是卡牌总数为3时的最大数,再将1000 与1进行按位或,即可得到1001 ,其十进制为9,就此我们即可得到比卡牌最大值多2的数值 。
- 1的二进制为
0001 ,例如我们需要的卡牌id 是2,那么通过左移运算1 <<( id –1 ) 可以实现0001 ->0010 的变换。此时我们就得到了第id 位为1,其余位置都为0的数,这也对应了E题中每张卡牌上的最小值的规律,由此我们即可得到第id张卡牌上的最小值 。
#include <stdio.h>
int main () {
//卡牌的总数量
int count_sum=0;
//需要查询的卡牌数量
int count_need=0;
//查询的卡牌位置
int id=0;
//输入卡牌的总数量以及需要查询的卡牌数量
scanf("%d %d", &count_sum, &count_need);
//根据卡牌总数量计算出比卡牌最大值多2的数值
int max_next_val = (1 << count_sum) | 1;
//循环 需要查询的卡牌数量 次
while (count_need--) {
//输入需要查询的卡牌位置id
scanf("%d", &id);
//计算出第id张卡牌上的最小值
int min_val = 1 << (id - 1);
//从1开始遍历到卡牌最大值
for (int i = 1; i != max_next_val; ++i) {
//如果第id位数字都为1,说明该数字满足条件,输出即可
if ((i & min_val) != 0)
printf("%d ", i);
}
printf("\n");
}
return 0;
}
解析:仔细观察二进制表达式,实际上有一个非常简单的二进制规律:
第1个:4 (DEC) -> 0100 (BIN)
第2个:5 (DEC) -> 0101 (BIN)
第3个:6 (DEC) -> 0110 (BIN)
第4个:7 (DEC) -> 0111 (BIN)
通过观察我们会发现,相邻的两个数
相邻格雷码之间的联系是它们的二进制表示
#include <stdio.h>
#include <limits.h>
typedef unsigned long long LLU;
LLU gray_code(int k, LLU i) {
LLU j = i - 1;
LLU left = j >> (k - 1);
LLU right = j & (ULLONG_MAX >> (64 - k + 1));
LLU result = (left << k) | (((LLU) 1) << (k - 1)) | right;
return result;
}
其中,
那么我们代入本题目,就可以这样理解:
- 我们输入
k 就可以规定格雷码的位数一定是k 位,且第k 位的数固定是1,比如我们输入的k 是3 ,那么他的起始二进制数值就是0100。 - 接着,我们输入
i 就代表着在起始值作为第一个数的基础上的,与它相邻的第i –1 个数,比如输入的i 是2 ,那么格雷码输出的就是与0100 相邻的第一个数,对应到题目中也就是第三张卡牌的第二个数字。 - 由此,我们可知:通过格雷码的计算,我们就可以得到与题目相邻二进制数值之间规律相同的数字了。
代码:
#include <stdio.h>
#include <limits.h>
typedef unsigned long long LLU;
int main() {
int k;
LLU i;
while (~scanf("%*d %d %llu", &k, &i)) {
if (k == 0 && i == 0) break;
/*此处是格雷码计算部分起始处*/
LLU j = i - 1;
LLU left = j >> (k - 1);
LLU right = j & (ULLONG_MAX >> (64 - k + 1));
LLU result = (left << k) | (((LLU) 1) << (k - 1)) | right;
/*此处是格雷码计算部分结束处*/
printf("%llu\n", result);
}
return 0;
}
当然,本题目还有第二个规律:每张卡牌上数字出现的规律都是连续出现
第1个:1 (DEC) -> 0001 (BIN)
第2个:3 (DEC) -> 0011 (BIN)
第3个:5 (DEC) -> 0101 (BIN)
第4个:7 (DEC) -> 0111 (BIN)
第一张卡牌的最小值为1,那么我们可以发现:第一个到第二个是,先出现一个数字1,然后跳过一个数字2,再连续出现一个数字3,也即:1
那么按照此规律,我们就可以来依据逻辑编写代码了:
#include <stdio.h>
typedef unsigned long long LLU;
int main () {
int k;
LLU i;
while (~scanf("%*d %d %llu", &k, &i)) {
if (k == 0 && i == 0) break;
LLU interval = LLU(1) << (k - 1);
LLU div = i / interval;
LLU mod = i % interval;
LLU result = (div * interval << 1) + (mod == 0 ? 0 : interval + mod) - 1;
printf("%llu\n", result);
}
return 0;
}
解析:本题目考察对
(1).C语言
#include <stdio.h>
typedef unsigned long long LLU;
int main() {
LLU var, real;
scanf("%llx %llo", &var, &real);
printf("%d", (int) (var - real));
return 0;
}
(2).Python
hex_year = input().strip()
oct_year = input().strip()
decimal_hex_year = int(hex_year, 16)
decimal_oct_year = int(oct_year, 8)
year_diff = decimal_hex_year - decimal_oct_year
print(year_diff)
以上解析内容中,部分代码来自出题人(kmjj)的官方博客:2023级新生周赛第二周题解
详细解析由本人编写,其中部分拓展代码也由本人编写,如有问题,请及时联系,喵~
暂无评论内容