CSU计算机学院2023秋C语言期中题目思路分享(前三道题)

写在前面

题目:中南大学计算机学院2023秋季C语言期中考考题(共六道,本文分享前三道的思路)
旨在分享自己的思考过程和一点经验,并给出一种输出正确的答案供大家讨论批判,有任何批评、问题可以直接在评论区留言或者直接私信告诉我,大家共同进步。
文字主要面向新手

A:个税计算——阅读理解与数据类型转换

原题

小南毕业工作了,他想知道自己每年的扣税情况。他查到的综合所得的个人所得税税率表如下:

级数全年应纳税所得额税率(%)速算扣除数
1不超过36000元的部分30
2超过36000-144000元的部分102520
3超过144000-300000元的部分2016920
4超过300000-420000元的部分2531920
5超过420000-660000元的部分3052920
6超过660000-960000元的部分3585920
7超过960000元的部分45181920

你能根据小南的收入算出他每年所要缴纳的税款吗?

输入

多个样例。每个样例输入一个整数m(104≤m≤107)代表小南的年终收入,为100的整数倍。

输出

每个样例输出一个整数,表示小南缴纳的个税。每个样例输出结果占一行。

样例输入
20000
36000
400000
660000
样例输出
600
1080
68080
145080

题目分析

这题有同学拿到手,没听过速算扣除数,不知怎么算。有同学知道了怎么算,提交只有80%,我们来看看。

题目理解

其实速算扣除数不论有没听过,怎么看都是要减去这个数的,怎么减?乘以税率之前还是之后?还有超过“的部分”,是不是要单独每部分算?其实这些问题全可以靠样例来解决——当题目关于计算方法、计算公式等没有用详尽的文字表述时,不需要太纠结,从样例输入输出可以判断出计算方法。

比如这题,大可以猜一猜怎么算,第一组第二组明显是20000×0.03,36000×0.03,没有问题。第三组400000,先别想太多,400000×0.25是100000,而结果是68080,也该看出来了,是100000减去对应的速算扣除数。

现在明白了,就是简单的乘以税率减去扣除数,带入第四组按一下计算器,没有问题。

代码实现与问题解决

大致涉及到这些知识:

  • 区间数if语句判断
  • 精度问题和解决
    我们一个个看
  1. if语句判断区间
    这时候有一种思想,按顺序判断,承上启下结构。什么意思呢?先判断是否大于A,else if是否大于B,此时由于else if只会在前一个if语句为假的时候判断,所以当判断这个“是否大于B”,就隐含了“不是大于A”的前提,也就是大于B,并且小于等于A。简单的思想,自己写过一次就能知道了。
    好处就是不用再写 大于B&&小于等于A了,结构也清晰。
    具体代码就是长这样:

    if(n > 960000){
    	t = n * 0.45 - 181920;
    }else if(n > 660000){
    	t = n * 0.35 - 85920;
    }else if(n > 420000){
    	t = n * 0.30 - 52920;
    }else if(n > 300000){
    	t = n * 0.25 - 31920;
    }else if(n > 144000){
    	t = n * 0.20 - 16920;
    }else if(n > 36000){
    	t = n * 0.10 - 2520;
    }else{
    	t = n * 0.03;
    }
    
    
  2. 精度问题和解决
    有同学看到百分比,下意识用了小数处理,这就要注意精度问题了。例如这样写,就会遇到错误80%,有的数算出差1:

#include <stdio.h>
int main(){
    int n;
    while(scanf("%d", &n) != EOF){
        int t;
        if(n > 960000){
            t = n * 0.45 - 181920;
        }else if(n > 660000){
            t = n * 0.35 - 85920;
        }else if(n > 420000){
            t = n * 0.30 - 52920;
        }else if(n > 300000){
            t = n * 0.25 - 31920;
        }else if(n > 144000){
            t = n * 0.20 - 16920;
        }else if(n > 36000){
            t = n * 0.10 - 2520;
        }else{
            t = n * 0.03;
        }
        printf("%d\n", t);
    }
    return 0;
}

这是因为在整数、小数之间转换的时候,会丢失精度,此时可以把t声明成float,输出使用%.0f既可。

当然,脑子转的快的同学看到题目中的“100的整数倍”,下意识的除以100,这是最好的,也就是在积算的时候先除以100,此时得到的一定是精确的整数,再乘以百分比,这样就不会损失精度了。

我的代码

#include <stdio.h>
int main(){
    int n;
    while(scanf("%d", &n) != EOF){
        float t;
        if(n > 960000){
            t = n * 0.45 - 181920;
        }else if(n > 660000){
            t = n * 0.35 - 85920;
        }else if(n > 420000){
            t = n * 0.30 - 52920;
        }else if(n > 300000){
            t = n * 0.25 - 31920;
        }else if(n > 144000){
            t = n * 0.20 - 16920;
        }else if(n > 36000){
            t = n * 0.10 - 2520;
        }else{
            t = n * 0.03;
        }
        printf("%.0f\n", t);
    }
    return 0;
}

或者:

#include <stdio.h>
int main(){
    int n;
    while(scanf("%d", &n) != EOF){
        int t;
        if(n > 960000){
            t = n / 100 * 45 - 181920;
        }else if(n > 660000){
            t = n / 100 * 35 - 85920;
        }else if(n > 420000){
            t = n / 100 * 30 - 52920;
        }else if(n > 300000){
            t = n / 100 * 25 - 31920;
        }else if(n > 144000){
            t = n / 100 * 20 - 16920;
        }else if(n > 36000){
            t = n / 100 * 10 - 2520;
        }else{
            t = n / 100 * 3;
        }
        printf("%d\n", t);
    }
    return 0;
}

B:时制转换——问题是一点点解决的

原题

12小时制和24小时制都是一种记录时间的方式,两者的区别在于:

(1)12小时制是把一天二十四小时分为上午和下午两个十二小时的时间段,上午从午夜12:00到上午11:59,后缀标记am或者AM,下午从正午12:00到晚上11:59,后缀标记pm或者PM。

(2)24小时制是把一天二十四个小时从午夜开始用00:00~23:59表示。

(3)使用12小时制显示时间时中午12点显示为12:00 pm或者12:00 PM,而将午夜显示为12:00 am或者12:00 AM。

小南的任务:将12小时制格式显示的时间转换为24小时制格式显示。

输入

多个样例。每个样例输入1行形式为hh:mmcd的数据,其中整数hh(00≤hh≤12)代表小时,整数mm(00≤mm≤59)代表分钟,cd是字母组合“am、AM、pm、PM”中的一个,分别表示上午(am或AM)和下午(pm或者PM)。

输出

每个样例输出新的24小时制格式的时间,形式为hh:mm。每个样例输出结果占一行。

样例输入
12:00am
12:59PM
11:09pm
03:02AM
04:18PM
样例输出
00:00
12:59
23:09
03:02
16:18

题目分析

拿到这个题目,大概脑子里会有两个问题:怎么读入数据?怎么转换?我们一个个看。

我们先读入数据:整数+冒号+整数+一个字符+一个字符。怎么读?%d:%d%c%c这样就行了!C语言先识别一个整数,遇到非数字后停止(这里是遇到了“:”,它发现这个不是数字,又把它放回去了),然后找一个冒号,再找一个整数,同理遇到p、P、a、A后停止。最后读两个字符。

这样分别存到四个变量后,就可以开始核心部分了。

怎么转换?转换就是把不同的东西变成相同的,什么不同?12小时和24小时最大的不同就在下午,也就是中午12点过后(pm),下午4点就是16点,下午9点就是21点——明显是+12。

这下明白了:只要pm(或者PM),把小时数加12就行了。

于是乎写出代码:

#include <stdio.h>
int main(){
    char a, b;
    int h, m;
    while(scanf("%d:%d%c%c", &h, &m, &a, &b) != EOF){
        if(a == 'p' || a == 'P'){
            h += 12;
        }
        printf("%02d:%02d\n", h, m);
    }
    return 0;
}

但是看看样例就知道出了问题:

  1. 12:00am应当是00:00,怎么办?好说,如果小时数是12,而且是am,那就变成0——除此之外,am都和24小时的小时数是一样的,比如早上3点就是3:00,这是一致的,除了12:00am。
  2. 12:00pm应当是12:00,怎么办?好说,当是pm,小时数不是12的时候,再加12就行了。
    我们说问题是一点点解决的,虽然这个问题脑子转得快的同学第一次看题就能看出来,但是如果一次想不到也没有关系,通过一步步修改、调试来完善代码,最后也能达到正确答案,这正是遇到复杂问题的解决方法。当然在不断的训练中也要有意识的想一想:漏了什么?想全了吗?有没有别的情况?尽量在测试之前自己找出一些问题,或者尽量提前预料到问题,提前规避。比如这一题,在题目中强调了12点的问题,是应当加以留意的。

我的代码

#include <stdio.h>
int main(){
    char a, b; // 存储pm、am
    int h, m; // 存储小时、分钟(分钟是没有用的)
    while(scanf("%d:%d%c%c", &h, &m, &a, &b) != EOF){ // 读入数据
        if((a == 'a' || a == 'A') && h == 12){ // 如果是am,并且小时数是12的话,小时数转成0
            h = 0;
        }
        if((a == 'p' || a == 'P') && h != 12){ // 如果是pm,而且小时数不是12的话,小时数加12
            h += 12;
        }
        printf("%02d:%02d\n", h, m); // 输出,%02d是用0来补位到两位
    }
    return 0;
}

C:统计进位——人教版小学二年级数学上册第二单元《进位加》的C语言实现

原题

老师发现很多学生在进行加法的时候,发现“进位”特别容易出错。于是交给小南一个任务,就是统计两个整数x和y在相加时需要多少次进位,其中x和y满足0≤x,y≤ 109,你能帮他完成任务吗?

输入

多个样例。 每个样例输入1行,包括2个整数x和y(0≤x,y≤ 109)。

输出

每个样例输出一个整数,代表所需要的进位数。每个样例输出结果占一行。

样例输入
8 7
9 0
154 246
555 5555
123 594
样例输出
1
0
2
3
1

题目分析

先来看看进位是什么:根据人教版小学二年级数学上册第二单元《进位加》知识可得,加法列竖式的时候,每一位相加(上一位有进位的还要加上上一位的进位1)大于10就是进一次位,以此类推。

例如9234+928:(从右看到左)
在这里插入图片描述

注意两点:

  • 后一次相加要加上前一次的进位1(如果前一次有进位),前一次没有进位可以看作加上0
  • 最后一位位数不足,可以补上0思考,比如上述例子928可以看作0928

很简单对吧!我的思路就是把列竖式的过程完完整整地搬到代码中。来看看吧!

数据读入与存储

首先想到的是需求,我要把两个数字右对齐“列竖式”,然后一位一位加。此时想想用int变量存储可以吗?当然可以,但是有点麻烦——每一位的提取很麻烦!看看这些对齐的数像什么?其实实质上就是一个二维数组,当然这里只有两行(只有两个数相加),用两个长度相同的数组也是可以滴。

此时用两个数组存储两个数的每一位就是第一步。当然可以用int数组,也可以用字符串数组,根据输入,用字符串显然方便一点:scanf("%s %s", a, b)就行了!

至于字符怎么转数字,也是一个经典用法,教材中也有提到:C语言的字符是用整型存储的,'0'就是48(在ASCII码下),'1'也就是49了,自然,用数字的字符减去'0',就是这个数的int型了!这样我们得到了两个数组,它们是这样的:

准备列竖式吧!

显然,这些数的长度不尽相同,默认读入的顺序明显是左对齐,这时候按照小学二年级的知识,我们要对它们进行右对齐:
在这里插入图片描述

明确一下数组的长度,题目要10位,保险起见设为100位吧!(doge)就可以写代码了(代码的功能就是上图的转换,应该是能明白的):

// 这里a1数组是a数组转换(右对齐)后的数组,b1同理
for (int i = 0; i < alen; i++) { // alen是用strlen函数获取的a数组的实际长度(也就是数字的长度,例如9234长度是4)
	a1[99 - i] = a[alen - i - 1];
}
for (int i = 0; i < blen; i++) {
	b1[99 - i] = b[blen - i - 1];
}

这种代码的核心就是带数字,往里带入几位试一试,就可以推出应该写什么了。比如a1的最后一位应该等于a的第4位,倒数第二位应该等于a的第3位,也就是a1[99] = a[3]a1[98] = a[2](注意数组的索引从0开始)。然后带入a、b的数组长度和变量i(i是从零开始增长的):a[99 - i] = a[alen - 1 - 1],数组的索引就是这样代入出来的。

好了,可以开始相加了。思考列竖式的过程,明确一下需要哪些变量:

  • 存储两数再加进位1(如果有的话)的变量k(就是判断k是否大于等于10的)。
  • 进位c,第一次加法c = 0,如果k大于等于0,c赋值为1,否则赋值为0,下一次算k的时候加上c就行了。

想一想,竖式从右到左的加法用什么实现?明显是for循环,循环多少次呢?观察列竖式的过程,不难看出,4位加3位,总共加了4次(包括最后一位加进位1)。想一想,4位加4位,总共加了4次;4位加10位,总共加了5次。——规律很清晰,如果两数位数相等,那就是循环次数就是两数位数;如果不相等,就是最短位数+1。

当然,你可以直接把所有列表中所有数字都循环遍历一遍,等等,是不是想到什么问题?——早该想到的!前面两个数不一样长的时候就该想到了——要保证数组中除了输入的数字外,其他都是'0'!这就需要在最开始时初始化a1、b1两个数组。这样就能保证不漏、不多,因为0+0不可能使得进位次数增多。

初始化是这样的:

for (int i = 0; i < 100; i++){
	a1[i] = '0';
}for (int i = 0; i < 100; i++){
	b1[i] = '0';
}

为什么不能char a1[100] = {'0'}?答案是这样只会把第一项令为'0',但是其余项会令成ASCII码为0的空字符'\0',这不是我们想要的。此时用循环才是保险。

竖式的加法计算

现在列好了竖式,开始计算吧!原理在上面陈述的很明白了,代码是这样的:

int k, c = 0, s = 0;                   // k、c上面说过了,s是存储进位次数的,也就是题目要输出的答案
for (int i = 99; i >= 0; i--) {        // 从最后一位开始循环,遍历整个列表(懒了,不想做判断了)
	k = a1[i] - '0' + b1[i] - '0' + c; // 计算k值,注意最后加上进位c,第一次计算的时候进位c为0
	if (k >= 10) {                     // 如果算出来大于等于10
		s++;                           // 统计进位次数+1
		c = 1;                         // 进位1
	} else {                           // 如果算出来在10以内
		c = 0;                         // 进位0
	}
}

结束了!我本题的思路就是模拟列竖式的方式进行计算。考试的时候中间犯了几次错误:

  • 转换右对齐的时候没整明白,把顺序颠倒了,9234对齐到右边变成4329了
  • 没有初始化a1、b1,导致错误
    当然遇到错误,先不要慌张,通过分段调试代码、找出原因、修改,都是可以解决的,问题是一点点解决的嘛。

我的代码

#include <stdio.h>
#include <string.h>
int main()
{
    char a[100], b[100], a1[100], b1[100];
    while (scanf("%s %s", a, b) != EOF) {
        for (int i = 0; i < 100; i++) {
            a1[i] = '0';
        }
        for (int i = 0; i < 100; i++) {
            b1[i] = '0';
        }
        int alen = strlen(a);  // 注意使用strlen要#include <string.h>
        int blen = strlen(b);
        // 这里a1数组是a数组转换(右对齐)后的数组,b1同理
        for (int i = 0; i < alen; i++) { // alen是用strlen函数获取的a数组的实际长度(也就是数字的长度,例如9234长度是4)
            a1[99 - i] = a[alen - i - 1];
        }
        for (int i = 0; i < blen; i++) {
            b1[99 - i] = b[blen - i - 1];
        }
        // 分位次加法
        int k, c = 0, s = 0;                   // k、c上面说过了,s是存储进位次数的,也就是题目要输出的答案
        for (int i = 99; i >= 0; i--) {        // 从最后一位开始循环,遍历整个列表(懒了,不想做判断了)
            k = a1[i] - '0' + b1[i] - '0' + c; // 计算k值,注意最后加上进位c,第一次计算的时候进位c为0
            if (k >= 10) {                     // 如果算出来大于等于10
                s++;                           // 统计进位次数+1
                c = 1;                         // 进位1
            } else {                           // 如果算出来在10以内
                c = 0;                         // 进位0
            }
        }
        printf("%d\n", s);
    }
    return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值