2023级E2练习赛

2023级E2练习赛

Tengpaz Lv3

2023级本科新生第二次练习赛习题讲解

改动

  • 2023/10/15 sizeof()是操作符不是函数

A 坐标系变换


原题如下

E2A

你需要知道

本题会用到

  • <math.h>头文件中的cossin两个函数
  • 数学里极坐标的基本知识
  • 标准输出里如何保留小数位数输出浮点数

cos()函数-求余弦

头文件:math.h
语法/原型:

1
double cos(double x);

这里cos前的double表示的是函数的返回值为double双精度实型
里面的double x表示这里函数的形式参数是double类型的实数
实际运用时,函数名前的double以及参数前的double都不用写,这里只是告诉你函数相关值的类型

sin()函数-求正弦

同理我们可以推到正弦函数是怎么使用的
头文件:math.h
语法/原型:

1
double sin(double x);

读者可以自行类比

极坐标

在极坐标中,我们用一个点到极点的距离ρ也即极径和过极点与该点的射线与极轴在逆时针方向所成的角度θ也即极角来描述一个点的位置,不难得到,极坐标转化为直角坐标后表示为(ρcosθ,ρsinθ).
极坐标

保留小数位数输出浮点数

来看看如下的输出语句

1
printf("%-6.2lf", x);

这个语句中

  • -表示左对齐
  • 6表示输出占6个字符
  • 2表示保留两位小数
    如果x=3.14123,输出如下
1
3.14

如果去掉6前面的负号
输出如下(下面的·表示空格)

1
··3.14

注意在使用时不要忘了两个数字中间的.,比如本题我们不需要调整输出的浮点数的对齐方式和占据的字符数,那么就可以把语句写成

1
printf("%.1lf",x);

这个就表示保留一位小数输出浮点数x

代码实现

1
2
3
4
5
6
7
8
#include<stdio.h>
#include<math.h>
int main(){
double a, b;//a表示极径,b表示极角
scanf("%lf%lf", &a, &b);
printf("%.1lf %.1lf", a * cos(b), a * sin(b));
return 0;
}

B 这里是BUAA 2


原题如下

E2B

你需要知道

  • 在ASCII码表中,大写字母数值连续,比如'B''A'大1
  • 读取多个字符

分析思路

对于读取的每一个字符,我们可以做一次判断,当读取的字符在'B''Z'之间时才需要把字母往前调一位

代码实现

这里不是最简单的版本,如果你喜欢数组和字符串的话
可以给你看看一个非常啰嗦的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
#include<string.h>//引入字符串相关函数的头文件

int main(){
char str[10001];
int n = 0;
gets(str);
n = strlen(str);
for(int i = 0;i <= n - 1;i++){
printf("%c",str[i] >= 'B' && str[i] <= 'Z' ? str[i] - 1 : str[i]);
}
return 0;
}

大佬直接跳,我这里稍作解释

strlen()函数

头文件:string.h
语法/原型:

1
size_t strlen(const char *str);

size_t表示函数返回字符串的长度,为unsigned int类型
const char *str是一个形式参数,类型是常量指针,constconstant的缩写,本意为不变的,不可改变的,表明该类指针指向的内存值不可改变
char *str是一个指向字符串的指针,如果目前没学指针的话,暂时可以理解为char str[],这里其实说明调用函数传参时传的是地址不是数值
这个函数的作用是返回字符串的长度,但需要注意的是,这个函数在取字符串长度时,读取到'\0'时就会停止,最后读取到的长度是第一个字符到第一个'\0'(不包括'\0')之间的字符串长度

这里举个例子

1
2
3
4
5
6
7
8
#include<stdio.h>
#include<string.h>

int main(){
char* p = "abcdef";
printf("%d",strlen(p));
return 0;
}

输出为6
再来举几个例子

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
#include<string.h>

int main(){
char* p = "abcdef";//[a b c d e f \0]
printf("%d\n",strlen(p));
printf("%d\n",strlen(p + 0));
printf("%d\n",strlen(p + 1));
printf(*p);
return 0;
}

输出

1
2
3
6
6
5

最后一个printf没有打印出来,因为指针p解码后是字符串中的一个字符,而strlen并不能对一个数据求长度

这里尤其需要与另一个操作符区分一下

sizeof()操作符

对于同一个合理字符串来说,sizeof操作符返回的字符串长度会包含结尾的'\0',而strlen不会包含,并且sizeof计算的是整个字符串占据的字节,并不会在'\0'处就此停下
来看一个例子

1
2
3
4
5
6
7
8
9
#include<stdio.h>
#include<string.h>

int main(){
char* p = "abcdef";
printf("%d\n",strlen(p));
printf("%d",sizeof(p))
return 0;
}

输出

1
2
6
7

gets()函数

头文件:stdio.h
语法/原型:

1
char *gets(char *str);

char *表明返回的是一个指向字符串的指针
里面的char *str说明传参时传入的是指向字符串str的地址
gets函数与scanf函数一样都是输入函数,但是两者还是会有很多差别的

  • scanf读取字符串是以空格作为结束标志的,这就决定了它无法读取完整的含有空格字符的字符串
  • gets也是读取一行字符串,但是它是以回车键作为结束标记的,所以它能读取含有空格字符的字符串

C GYCY的乘法口诀表


原题如下

ACE2C

你需要知道

  • for循环遍历

分析

很明显需要迭代两个for循环进行遍历,这里不多说,直接上代码

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>

int main(){
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++){
for(int j = 1;j <= i;j++){
printf("%d*%d=%d ", i, j, i * j);
}
printf("\n");
}
return 0;
}

D 小亮的圆周率


原题如下

ACE2D

你需要知道

  • 各类数据类型及数据表示范围
  • 强制类型转换
  • 如果你想,可以使用fabs函数求两个浮点数之差的绝对值
  • 如果你想练习函数,可以故意把代码搞麻烦点弄两个计算不同公式不同项数值的函数
  • 多个数据输入
  • 保留特定小数位数输出
  • 如果你想,可以使用pow()函数计算次方

fabs()函数

头文件:math.h
语法/原型:

1
double fabs(double x)

这个函数可以用来返回浮点数x的绝对值,这样我们在做这道题时可以不用比较两个公式计算值的大小关系了
不过使用这个函数时要注意,我们看这个函数声明可以发现,传参是有要求的,fabs函数可以用于doublefloatlong double类型的参数如果你用它来求整数绝对值,最后求得的结果将带上小数
如果你以后需要计算某个整数的绝对值,可以选择使用abs()函数,这里就不详细展开了

pow()函数

头文件:math.h
语法/原型:

1
double pow(double x, double y);

同样我们看函数声明可以发现,该函数返回值为double类型,传递的参数类型为double类型,计算出的结果是x的y次幂

数据类型及表示范围

我们仔细观察可以发现,n的取值可以非常大,加上公式中出现的(2n+1)^2^数值将会达到4x10^10^,int能容纳得下吗?我们需要使用long long啦

  • int类型数据所占内存大小为4字节32位,其中一位为符号位,计算机是以二进制储存数据的,我们通过计算可以知道int最大允许的取值为2^31^-1,好的,不够用了
  • long long类型数据所占内存大小为8字节64位,这就完全够用了

保留小数位数

详见A

强制类型转换

你所需要的最终数据的类型可能与你最初给的数据的类型不同,这时候便需要转换数据的类型了,格式如下
(<数据类型>)x
这表示把x的数据类型强制转换为括号内的数据类型

代码实现

这里给出的只是正确代码,不是最佳代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<stdio.h>
#include<math.h>

double x(int j);
double y(int j);

int main(){
int t;
int n;
scanf("%d", &t);
for(int i = 1; i <= t; i++){
scanf("%d",&n);
printf("%.6lf\n",fabs(x(n)-y(n)));
}
return 0;
}

double x(int j){
double sum=0;
for(;j >= 1; j--){
sum+=pow(-1,j - 1)/(2 * j - 1);
}
return 4*sum;
}

double y(int j){
double sum=0;
for(;j>=1;j--){
sum+=(double)1/((long long)(2 * j - 1)*(2 * j - 1));
}
return sqrt(8 * sum);
}

E 体能锻炼走廊


原题如下

ACE2E

你需要知道

  • 多组多个数据输入
  • 对前次循环进行数值清除

多次循环

当你的同一个变量在多次循环中出现并作为一种判断条件,你就要注意了,如果你清楚地知道你需要用该变量做什么,你就要关注这个变量是否会带着上一轮的数据进入下一轮并作为条件进行判断了,因为这很容易导致逻辑错误而导致结果不如人意,并且这种错误很难被人察觉

分析思路

其实思路挺简单,主要就是对条件语句的运用了

代码实现

这里是正确代码,不是最佳代码,不是最简代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<stdio.h>

int main(){
int n, m, below = 0, up = 0;
scanf("%d",&n);
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= 6; j++)
{
scanf("%d",&m);
if(m >= 48){
up = m-48;
if(below <= 0){
below += up;
if(below > 0)
below = 0;
}
}else
below += m - 48;
}
if(below == 0)
printf("Success\n");
else if(below < 0)
printf("Failure %d\n",-below);
below = 0;//清除前一轮的数据
up = 0;
}
return 0;
}

F Wings 咖啡


原题如下

ACE2F

你需要知道

  • 对于多种极端情况的考虑要求周全
  • 数据范围
  • 如何防止爆栈
  • 全局变量
  • 对数据最大值的求取

情况极端

  • 虽然说你可能凭直觉觉得6元肯定是划算的价格,不然就不会把每天用校园卡买的第一杯咖啡定为6元了,但是!!!这是编程题目不是实际情况,咖啡价格确实很有可能小于6元(bushi
  • 虽然说去找卡的那个人大概明白需要多少张卡,但是这也避免不了找到的卡的数量比需要的卡的数量要更多
  • 本人的卡和人头数你算上了吗?
  • 你有注意数据有多大吗?
  • 你有注意需要的数组的长度吗?

数据范围

之前强调过就不再强调了

爆栈

c语言的函数调用机制是依靠堆栈来实现的,称为函数调用栈(栈空间),程序中函数的局部变量存放在栈空间中,但是,每一个函数的栈空间有限,Windows系统下通常是2MB,所以,局部数组不能开得太大了!!!
像这道题,如果你把一个长度为3000001的int型数组作为局部数组扔那个小小的main函数栈空间里(辛苦你了),后果长啥样应该能知道了叭,这就是爆栈,在Debug中应该是被软件识别为segmantation fault(储存器区块错误),你的程序就运行不了啦

解决这个问题其实也很简单
你放函数里面会爆,我扔函数外面就没事了嘛
这就是我们接下来要讲的-全局变量

全局变量

我们之前提到了,函数里边定义的变量其实是局部变量,为什么叫局部呢,因为这个变量的生命周期,是随着函数的调用与结束而开始和终结的,也就是说,你函数一用完,这变量就没了
而全局函数不同的是,它是定义在整个程序中而不是某个函数中的,也就是说,它的生命周期随这个程序的运行情况而定,只有当程序运行完后,变量才会“死亡”
并且,理论上讲,全局变量能使用的空间是无限的(当然得要小于你电脑的内存啦awww),所以你可以放心大胆得开你的超长数组。。。

最大值的求取

其实在c语言里有这个函数的文件的,但这个函数的头文件叫algorithm,如果你用的编译软件是VScode,这个软件可能找不到这个文件的位置,需要你进行配置,这里就不展开说明啦
所以就别躲了,上循环硬刚叭

本题需要的不仅仅是最大值,而是最大的m+1个值,所以我们可以在每循环一次求得了最大值后,选择把最大值处理掉,使上一个最大值不再影响第二次最大值的求取

代码实现

这不是最优的,但是是正确的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<stdio.h>
int a[3000001]={0};
int main(){
int n,m,max,num;
long long sum = 0;//sum的数值后期非常大
scanf("%d%d", &n, &m);
num = n+1;
for(int i = 0; i < num; i++){
scanf("%d", &a[i]);
if(a[i] <= 6){//算出咖啡价格大于6的人头数
n--;
sum += a[i];
a[i] = 0;//把数值清零,后面就不会重复加和了
}
}
if(m >= n){
sum += 6 * (n+1);//卡足够,全部置为6
}else{
for(int j = m + 1; j >= 1; j--){
for(int i = 0; i < num; i++){
if(i == 0||a[max] < a[i]){//取最大值
max = i;
}
}
a[max] = 6;
}
for(int i = 0; i < num; i++){
sum += a[i];
}
}
printf("%lld", sum);
return 0;
}

G 某咸鱼与投资


原题如下

ACE2G

你需要知道

  • 输出数字的前导零
  • 字符串相等的判断-strcmp()函数
  • 你知道闰年的规则嘛

strcmp()函数

头文件:string.h
语法/原型:

1
int strcmp(const char *str1, const char *str2);

从声明中我们可以知道,这个函数返回一个int型整数值,传参为两个指向字符串的常量指针

返回值的规则如下

  • 如果str1 > str2,返回正数
  • 如果str1 = str2,返回0
  • 如果str1 < str2,返回负数

其中,两个字符串比较时从第一个字符开始比较,如果相同则比较下一个

  • 遇到不同的字符,排在字母表前面的字符小于排在后面的(可以认为是比较字符的ASCII码)
  • 如果两个字符串所有字符都相同,则二者相等
  • 如果一个字符串提前结束,那么一定是长的那个字符串大于短的那个字符串

输出前导零

比如%0md,表示以整型输出时在数字前补充前导零,使其总位数为m位,m也即位宽

闰年

对于闰年来说,它能整除4且不能被100整除,或者它能整除400,则这个年为闰年

思路

其实主要就是需要判断2月的天数,大月小月判断,以及延期是早于时间点还是晚于时间点,这些用普通条件语句可以解决啦
看代码实现叭

代码实现

这是正确的,不是最好的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include<stdio.h>
#include<string.h>

void runnian(int);//判断是否为闰年的函数
int ifdelay(char*, int, int, int);//判断延期类型的函数
int monthdays(int);//判断月的天数的函数
void after(char*);//日期后移的函数
void print();//输出函数

int a;//闰年情况,全局变量
char mon[] = "Mon", tue[] = "Tue", wed[] = "Wed", thu[] = "Thu", fri[] = "Fri", sat[] = "Sat", sun[] = "Sun";
int year, month, day, hour, minute, second;

int main(){
char litter;//吞掉空格,或者你也可以在输入代码后面做改动
scanf("%d/%d/%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second);
scanf("%c",&litter);//清除空格
char date[4];//日期,注意把字符串结束符括进去
gets(date);
//判断闰年
runnian(year);
//延期情况分类
if(ifdelay(date, hour, minute, second)){
print();
}else if(ifdelay(date, hour, minute, second) == 0){//延期
if(strcmp(date, sat) == 0){//周六
strcpy(date,mon);
if(day <= monthdays(month) - 2){
day += 2;
print();
}else if(month != 12){
month ++;
day = day + 2 - monthdays(month);
print();
}else{
year++;
day = day + 2 - monthdays(month);
month = 1;
print();
}
}else if(strcmp(date, sun) == 0){//周日
strcpy(date,mon);
if(day <= monthdays(month) - 1){
day++;
print();
}else if(month != 12){
month ++;
day = 1;
print();
}else{
year++;
day = 1;
month = 1;
print();
}
}else if(strcmp(date, fri) != 0){//周一到周四
after(date);
if(day <= monthdays(month) - 1){
day++;
print();
}else if(month != 12){
month ++;
day = 1;
print();
}else{
year++;
day = 1;
month = 1;
print();
}
}else{//周五
strcpy(date,mon);
if(day <= monthdays(month) - 3){
day += 3;
print();
}else if(month != 12){
month ++;
day = day + 3 - monthdays(month);
print();
}else{
year++;
day = day + 3 - monthdays(month);
month = 1;
print();
}
}
}else{//前类延期
print();
}
return 0;

}
//闰年函数定义
void runnian(int y){
if((y % 4 == 0 && y % 100 != 0) || y % 400 == 0){
a = 1;
}else{
a = 0;
}
return;
}
//延期判断
int ifdelay(char* str, int h, int m, int s){
if(strcmp(str,sat) == 0 || strcmp(str,sun) == 0){
return 0;//后移
}else if((h >= 9 && h <= 14) || (h == 15 && m == 0 && s == 0)){
return 1;//不延期
}else if(h < 9){
return -1;//前类
}else{
return 0;//后类
}
}
//月天数
int monthdays(int m){
if(m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12){
return 31;
}else if(m == 2){
return a == 1 ? 29 : 28;
}else{
return 30;
}
}
//日期后移
void after(char* str){
if(strcmp(str,mon) == 0){
strcpy(str,tue);
}else if(strcmp(str,tue) == 0){
strcpy(str,wed);
}else if(strcmp(str,wed) == 0){
strcpy(str,thu);
}else{
strcpy(str,fri);
}
return;
}
//输出函数
void print(){
printf("%04d/%02d/%02d", year, month, day);
return;
}

H 摩卡背单词


原题如下

ACE2H

你需要知道

  • 对于重复字符的判断转化为数字确实会容易很多,因为数字是计算机可以直接识别的整体,而字符串计算机无法直接判断是否相同
  • 输入的东西在计算机里的储存方式
  • 如何清除掉换行符

字符转化为数字

如何把字符串翻译成数字呢?

ASCII码

为了不保证字符位置的改变造成的数字重复,同时也保证数字不要过于大,我们可以把不同位置的字符减去一个合理的相同的数字后分别乘以一个不同的数来转化为数字

比如说moca

可以把每个字符先减97

第一个字符乘1000000

第二个字符乘10000

第三个字符乘100

最后一个不乘

结果加和就是我们翻译出来的数字啦嘿

输入的东西在计算机里是如何储存的

比如我们按照题目示例那样,在第一行输入一个6后按回车键,那么这一输入在计算机里储存实际上大概长这样

[6|\n]

此时如果你没有处理掉回车键

你就可能在后面读取字符时把回车键一起读取进去,导致答案错误

处理掉回车键换行符

我们可以使用getchar()函数,因为这个函数读取字符只读取一个,也就是说,它只会读取到\n,从而可以很好地解决到行尾换行符回车键的问题

getchar()函数

头文件:stdio.h
语法/原型:

1
int getchar(void);

我们来看这个函数的声明,不难发现,getchar返回值是字符的ASCII

getchar用于读取单个字符,如果此时缓存区(如果不清楚就暂且把它理解为暂时储存你输入的东西的地方)中有多个字符,那么getchar读取的就是上一次读取的最后一个字符的后一个字符

思路分析

既然我们已经可以把字符串转化为数字了,那我们就可以用数组来判断是否重复的问题

比如如果39出现过了,我们就可以把数组的第39个元素设为1,其他位置的数据在刚开始时全部设为0,这样再次遇见39时,我们只要判断该数组的第39个元素是否为1即可

代码实现

这不是最优代码,但是是正确代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<stdio.h>

int x[26262627] = {0};

int main(){
int n;
long long m;
char a, b, c, d;
scanf("%d", &n);
for(int i = 0; i < n; i++) {
getchar();//清除换行符
scanf("%c%c%c%c", &a, &b, &c, &d);
m = (a - 97) * 1000000 + (b - 97) * 10000 + (c - 97) * 100 + d;//翻译为数字
if (x[m] == 1) {
printf("Moca has already memorized this word!\n");
} else {
x[m] = 1;
printf("Moca memorized a new word!\n");
}
}
return 0;
}

最小内存版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<stdio.h>

int readBit(unsigned int bit,char *array){//读取字节
int shift = 7 - (bit % 8);
return ((array[bit / 8 ]>>shift)&1);
}

void writeBit(unsigned int bit,int value,char*array){//压缩字节
if(readBit(bit,array)==value){
;
}else{
array[bit/8] ^= (128>> (bit % 8));
}
}
//这样可以做到把一个数字压缩为一个bit储存于数组内存的不同位中,达到减小内存的目的
char wordBank[57122] = {'\0'};

int main(){
int n,position;
char a, b, c, d;
scanf("%d", &n);
for(int i = 0; i < n; i++) {
getchar();
scanf("%c%c%c%c", &a, &b, &c, &d);
position = (a - 'a')*26*26*26 + (b - 'a') *26*26 + (c - 'a') * 26 + d-'a';
if (readBit(position,wordBank)) {
printf("Moca has already memorized this word!\n");
} else {
writeBit(position,1,wordBank);
printf("Moca memorized a new word!\n");
}
}
return 0;
}

I 规则的形


待更新哦

  • 标题: 2023级E2练习赛
  • 作者: Tengpaz
  • 创建于 : 2023-09-24 13:52:36
  • 更新于 : 2023-10-15 10:28:54
  • 链接: https://qinaida.cn/tengpaz/2023/09/24/2023级E2练习赛/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论