No.16 -No.20 C语言从入门到竞赛精通

No.16 -No.20 C语言从入门到竞赛精通

| No.16 求s=a+aa+aaa+aaaa+aa…a的值,其中a是一个数字。例如2+22+222+2222+22222(此时共有5个数相加),几个数相加由键盘控制。

题目分析

在本题中我们需要求一系列叠数相加之和,本题解法很多,我们将使用较为简单直观的 对数字进行处理 的方法,并在最后打印出整个相加求和的流程与结果,由于本题只需要最终的结果,且我的代码中存在对字符串进行处理的部分,为了便于小伙伴们的阅读,我已经在代码中的字符串拼接部分进行注明,如果您并不了解字符串拼接部分的内容,您可以忽略该部分的实现。

回到正题,对于构造叠数的问题,由于本题选择对数字进行处理,我们可以采用以下逻辑实现:

第 i 个位置上的叠数 = 第 i-1 个位置上的叠数 * 10 + 目标数字

使用一个临时变量tmp存储当前位置的叠数,则下一个叠数 = tmp * 10 + 目标数字

在了解完以上逻辑之后,我们来看代码实现:

#include <stdio.h>
#include <string.h>

int main() {
    int num = 0;
    int cnt = 0;
    int res = 0;
    char str[101] = "";  // 用于存储计算过程的字符串 
    char guo[101] = ""; // 用于临时存储计算过程中的叠数 

    printf("请输入a和次数,以空格分割\n");
    scanf("%d %d", &num, &cnt);

    int tmp = num; // 临时变量tmp,记录 

    for (int i = 0; i < cnt; i++) {
        // 添加加号分隔符(第一次循环不加)
        if (i > 0) {
            strcat(str, "+"); // strcat是C语言中的字符串拼接函数 
        }
        // 使用sprintf对字符串进行构造, 
        sprintf(guo, "%d", tmp);
        strcat(str, guo);
        res += tmp;
        tmp = tmp * 10 + num;
    }

    printf("计算过程:\n");
    printf("%s ", str);
    printf(" = %d\n", res);
    printf("最终计算结果是:%d", res);
    return 0;
}

 

| No.17 一个数如果恰好等于它的因子之和,这个数就称为“完数”。例如6=1+2+3.编程找出1000以内的所有完数。

题目分析

在本题中,题目规定了一个新的概念 “完数” ,并对其概念做了解释:一个数如果恰好等于它的因子之和,有小伙伴私信说自己碰到很多这种新概念的题目,她说自己很烦恼,因为这类题目的新概念很多,她没办法都背下来

 

在这里插入图片描述

在这里博主要小小的提一下,这类所谓的新概念题目是并不需要我们去将他的概念给背下来的,一般稍微正规一些的题目在题眼中都会将其定义告诉我们,所以小伙伴们不需要为此烦恼,具体问题具体分析,随机应变即可

言归正传,本题其核心之处就在于,需要我们求解每一个数的每一个因数,且根据题中的例子,1应该算是每一个数字的因数,且这个数字本身并不算在因数之内,所以会有 “6=1+2+3” 这个例子,由于题中所给的目标范围为 “1000以内” ,这个范围比较小,我们可以采取最简单粗暴的方法求解某一个数字的所有因数,即:

使用循环遍历从 2 到 目标数字-1 这个范围内的所有数字,只要目标数字能循环变量整除,则该循环变量即为目标数字的因数

通过这个方法,我们定义一个额外的变量,用于记录这些因数的和,只要最终其值等于目标数字,则判断该目标数字为“完数”

在了解完以上逻辑之后,我们来看代码,在以下的代码中,我对满足“完数”条件的数字其因数进行打印,以印证代码输出的结果:

#include<stdio.h>
#include<math.h>

void show(int* arr, int n){
    // 输出目标数字的因数求和序列 
    for (int i=0; i<n; i++){
        printf("%d ", arr[i]);
        if (i != n-1){
            printf("+ ");
        }
    }
    printf("= ");
}

bool is_wanshu(int num){
    int res = 1;
    int arr[1001] = {1}; // 记录因数序列,初始化在完数判断的函数中 
    int n = 1; // 用于记录因数数组当前存储因数的位置 

    for (int i=2; i<num; i++){
        if(num % i == 0){ // 因数 
            res += i; // 因数求和 
            arr[n] = i; // 存储因数 
            n ++; 
        }
    }

    if (res == num){
        show(arr, n); // 调用show函数,用于打印因数序列 
        printf("%d\n", res);
        return true;
    }

    return false;
} 


int main(){
    int cnt = 0;
    for (int i=2; i<=1000; i++){
        if (is_wanshu(i)){ // 满足完数条件 
            printf("完数: %d\n", i);
            cnt ++;
        }
    }
    printf("cnt: %d", cnt);
} 

在这里插入图片描述

|No.18 一球从100米高度自由落下,每次落地后反跳回原高度的一半;再落下,求它在第10次落地时,共经过多少米?第10次反弹多高?

题目分析

在本题中,您将会看到一个小球自 100m 高处被放下,经历若干次反弹,本题中需要我们计算 小球第十次落地 时的弹跳总路程以及第十次反弹的高度,具体示意图如下:

在这里插入图片描述

在这里我们可以发现, 第一次落地时经过的路程为一开始的100m,每一次落地,小球弹跳的高度为原有的一半,从弹起到落地,每次经过的路程为两段弹起的高度 ,尤其需要注意 从弹起到落地,每次经过的路程为两段弹起的高度 这个点,因为有一些小伙伴在提交本题的代码时由于没考虑到 “下落的路程” 这块的内容,导致提交结果全部爆红的现象

在这里插入图片描述

在有了以上分析之后,相信您对于本题的解法也形成了比较清晰的逻辑,我们直接来看代码:

#include <stdio.h>

int main() {
    double high = 100.0;
    double total = high; // 初始下落100米
    for (int i = 1; i < 10; i++) { // 处理第2到第10次落地,共9次循环
        high /= 2;         // 反弹后的高度
        total += 2 * high; // 每次反弹 +下落的路程
    }
    printf("第十次落地时,共经过:%f m,第十次反弹高度为:%f m\n", total, high / 2);
    return 0;
}

在这里插入图片描述

| No.19 猴子吃桃问题:猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想再吃时,见只剩下一个桃子了。求第一天共摘了多少。

题目分析

本题为经典的递归入门问题,对于递归如何实现,本处不做详细介绍,这部分内容请小伙伴们自行学习,那么对于本题,我们直接来说一个误区:

有很多小伙伴正向思维,认为:猴子每次吃的是前一天的一半,然后因为嘴馋多吃了一个,因此,在递归逻辑中,其返回的是:

return 2 * sum_peach(day+1) + 1;

提问:这里的正向逻辑哪里不对呢?请小伙伴们思考一下哦

接下来,我们使用正规的数学逻辑来推导一下递归的最终表达式:

我们设当前天和后一天分别为:$x_n 和 x_{n+1}$ ,则有:
$$x_{n+1} = x_n/2 – 1$$

通过一个简单的移项,我们就可以得到:
$$x_n = 2 * ( x_{n+1} + 1)$$

上述公式即是正确的递归计算式,博主在这里顺带提一句,递归最终语句,一般都是依靠严谨的递推式,如果能用数学推导尽量使用推导哦!

在理解上述逻辑推理之后,我们来看看代码,本处代码使用while循环实现,我希望小伙伴们可以动手试试,将循环转换成递归的写法,在大多数情况下,这二者都是可以相互转换的哦:

#include<stdio.h>

int main()
{
    int day,x1,x2;
    day=9;
    x2=1;
    while(day>0){
        x1=(x2 + 1) * 2;/*第一天的桃子数是第2天桃子数加1后的2倍*/
        x2=x1;
        printf("第 %d 天的桃子数: %d\n", day, x2);
        day--;
    }
    printf("桃子总数: %d\n",x1);
}

在这里插入图片描述

如果您对循环转换成递归有困难,也可以参考以下代码:

#include<stdio.h> 

int get_nums(int day){
    if (day == 10){ // 第十天只剩下一个桃子,本条件为递归出口
        return 1;
    }
//  int last_day = 2*(get_nums(day+1)) + 1; // 错误推导 
    int last_day = 2*(get_nums(day+1) + 1);
    printf("第%d天的桃子数:%d\n", day, last_day);
    return last_day;
}


int main(){
    int res = get_nums(1);
    printf("共有 %d 个桃子", res);
}

在这里插入图片描述

|No.20 两个乒乓球队进行比赛,各出三人。甲队为a,b,c三人,乙队为x,y,z三人。已抽签决定比赛名单。有人向队员打听比赛的名单。a说他不和x比,c说他不和x,z比,请编程序找出三队赛手的名单。

题目分析

本题要求我们根据题中的一些附加条件,为两支队伍中的选手匹配对手,逻辑比较简单,只需要注意:

  • 1,对手不能相同
  • 2,满足题中的先决条件

要实现上述逻辑比较简单,我们也可以手动推演得到答案,现在我们从一个编程学者的角度打开本题,我们直接采用三个循环实现,为甲队的三位选手依次选择满足条件的乙队选手:

#include <stdio.h>

int main() {
    char a_opponent, b_opponent, c_opponent;

    // 使用三个for循环遍历所有可能的对手组合
    for (a_opponent = 'x'; a_opponent <= 'z'; a_opponent++) {
        for (b_opponent = 'x'; b_opponent <= 'z'; b_opponent++) {
            for (c_opponent = 'x'; c_opponent <= 'z'; c_opponent++) {
                // 检查对手是否互不相同
                if (a_opponent != b_opponent && 
                    a_opponent != c_opponent && 
                    b_opponent != c_opponent) {
                    // 应用题目中的约束条件
                    if (a_opponent != 'x' &&          // a的对手不是x
                        c_opponent != 'x' &&          // c的对手不是x
                        c_opponent != 'z') {          // c的对手不是z
                        printf("比赛名单为:\n");
                        printf("a vs %c\n", a_opponent);
                        printf("b vs %c\n", b_opponent);
                        printf("c vs %c\n", c_opponent);
                    }
                }
            }
        }
    }
}

在这里插入图片描述

那么以上就是本期 C语言从初识到竞赛精通 No.16-No.20 的全部内容了,如果您在阅读本文的过程中有所收获,或者有任何宝贵的建议和想法,欢迎通过邮箱、微信或者留言等方式给我留言交流,您的每一次建议都将是我前进的动力!

在这里插入图片描述

上一篇
下一篇