蓝桥杯C语言竞赛3万字实战经验指南:从零基础到国一的全链路攻略

前言:蓝桥杯C语言竞赛的“含金量”与备战逻辑

蓝桥杯C语言竞赛(本科B组为主,兼顾A/C组)是国内高校理工科最具性价比的编程赛事之一——低门槛入门,高认可度收尾,尤其适合大一、大二零基础起步,想通过竞赛提升编程能力、丰富简历的学生。其核心特点是“重基础、轻算法,强实战、弱技巧”,80%的获奖选手靠的不是天赋,而是系统化的刷题策略和对竞赛套路的精准把控。

本文绝非“泛泛而谈的经验”,而是一套可落地的“竞赛备战行动手册”,涵盖从环境配置到真题拆解、从知识点盲区扫雷到临场应试技巧的全流程,总字数超3万字,所有内容均基于近10年蓝桥杯C语言真题(2015-2025)提炼,附100+道高频考点例题及代码模板,帮你避开90%的备考坑,用最短时间突破省一、冲击国赛。

第一部分:赛前认知篇——搞懂蓝桥杯,少走半年弯路

1.1 蓝桥杯C语言竞赛的核心信息(必看,避免报名踩坑)

• 参赛组别:本科分A/B/C三组,专科分D/E组;零基础/大一建议从B组起步,A组适合有算法基础(如学过《算法竞赛入门经典》)的学生,C组难度最低(部分院校不认可C组奖项,报名前需确认本校政策)。

• 竞赛流程:省赛(每年3-4月)→国赛(每年5-6月),省赛获奖(省一/二/三)可晋级国赛,国赛获奖(国一/二/三)有现金奖励(国一5000元,国二3000元)及企业校招绿色通道(华为、字节、京东等均认可)。

• 考试形式:全程机考,时间4小时,共10道题(近年题型稳定):

◦ 前5题(客观题):填空/选择题,每题5-10分,考基础语法(数组、指针、函数)、简单逻辑计算(日期、进制转换),送分题,目标满分;

◦ 后5题(编程题):主观题,每题15-25分,考算法应用(枚举、递归、动态规划、图论入门),按测试用例得分,部分得分也能拉开差距。

• 评分规则:客观题只有“全对/全错”,编程题按“通过的测试用例数量”给分(即“暴力能过一半用例,就拿一半分”),不卡代码风格,只看结果是否正确。

1.2 蓝桥杯C语言与平时课堂C语言的3个核心区别

很多同学课堂C语言学得不错,但第一次参赛却拿不到奖,核心是没搞懂“竞赛C语言”和“课堂C语言”的差异:

1. 目标不同:课堂追求“代码规范、可读性强”,竞赛追求“快速AC(Accepted)、用最少代码解决问题”——比如课堂写排序会封装函数,竞赛直接写冒泡/快排模板,能省10分钟;

2. 考点侧重不同:课堂重点讲“指针、结构体、文件操作”,竞赛重点考“枚举、递归、动态规划、数学计算(质数、公约数)”——文件操作几乎不考,指针仅在难题中出现;

3. 调试方式不同:课堂可以一步步单步调试,竞赛只能靠“输出中间结果”调试(机考环境没有单步调试功能)——这也是很多同学竞赛时写不出题的关键,平时没练过“无调试写代码”。

1.3 零基础备战蓝桥杯的3个阶段(附时间规划表)

阶段1:基础扫盲期(1-2个月,每天2小时)

• 目标:掌握竞赛必备的C语言语法,能独立写出100行以内的简单程序(如求1-100的质数、冒泡排序);

• 核心任务:

1. 学完《C语言程序设计(谭浩强)》第1-8章(跳过文件操作、位运算,重点学数组、函数、循环);

2. 刷完“蓝桥杯基础语法题”50道(推荐平台:蓝桥杯官网题库→“入门训练”板块);

• 时间规划:每天1小时学语法,1小时刷题,周末可翻倍(避免“只学不练,眼高手低”)。

阶段2:算法攻坚期(2-3个月,每天3小时)

• 目标:掌握蓝桥杯高频算法(枚举、递归、动态规划等),能解决80%的编程题(前8题);

• 核心任务:

1. 学《算法竞赛入门经典(刘汝佳)》第1-5章(重点看“枚举、递归、贪心、动态规划入门”);

2. 按“算法类型”刷真题(如本周刷“枚举类真题”,下周刷“递归类真题”),每类刷20-30道;

• 时间规划:每天1小时学算法理论,2小时刷题(每道题先独立想30分钟,想不出再看题解,看懂后脱稿重写)。

阶段3:真题冲刺期(1个月,每天4小时)

• 目标:适应4小时竞赛节奏,熟悉真题套路,查漏补缺;

• 核心任务:

1. 每周做2套近5年省赛真题(严格计时4小时,模拟真实竞赛环境);

2. 整理“错题本”和“代码模板本”(如快速幂模板、质数判断模板、动态规划模板);

• 时间规划:每天2小时做真题,1小时改错题,1小时背模板(模板一定要背熟,竞赛时直接默写,节省时间)。

第二部分:基础语法篇——竞赛必掌握的C语言核心考点(附避坑指南)

2.1 输入输出:竞赛中最容易踩的5个坑

输入输出是蓝桥杯客观题和编程题的基础,90%的新手第一次参赛会在这里丢分,核心是没掌握“竞赛级输入输出技巧”:

2.1.1 scanf的3个高频坑点及解决方案

• 坑点1:输入整数时,忽略空格/换行符导致读取错误(如输入“1 2 3”,用scanf("%d%d%d")没问题,但输入“1\n2\n3”就出错);

◦ 解决方案:scanf默认忽略空格和换行,只要格式符对应,无论输入时怎么换行/空格,都能正确读取,无需额外处理;

• 坑点2:输入字符时,读取到“回车符”(如先输入整数n,再输入字符c,代码:scanf("%d", &n); scanf("%c", &c); 此时c会读取到n后面的回车);

◦ 解决方案:两种方式二选一:

1. 在%c前加空格:scanf(" %c", &c);(空格会吸收前面的回车/空格);

2. 用getchar()吸收回车:scanf("%d", &n); getchar(); scanf("%c", &c);

• 坑点3:输入字符串时,遇到空格就停止(如输入“hello world”,用scanf("%s", str)只能读取到“hello”);

◦ 解决方案:用fgets()读取整行(注意fgets会读取换行符,需手动去掉):
char str[100];
fgets(str, 100, stdin); // 读取整行,包括换行符
str[strlen(str)-1] = '\0'; // 去掉换行符
2.1.2 printf的2个实用技巧(省赛必考)

• 技巧1:按“固定位数”输出(如输出123,要求占5位,不足补空格:printf("%5d", 123); 输出“  123”;不足补0:printf("%05d", 123); 输出“00123”)——日期题、进制转换题必用;

• 技巧2:输出浮点数时控制小数位数(如输出3.1415926,保留2位小数:printf("%.2f", 3.1415926); 输出“3.14”)——计算题必用。

2.1.3 输入输出效率:当输入数据量大时,用scanf/printf而非cin/cout

蓝桥杯部分编程题(如第7-8题)输入数据量很大(1e5个数字),用cin/cout会超时,必须用scanf/printf;若习惯用cin,需在main函数开头加:
ios::sync_with_stdio(false);
cin.tie(0);
(关闭同步,加快cin速度,但此时不能混用scanf和cin)。

2.2 数组:竞赛中用得最多的“数据容器”

数组是蓝桥杯的核心考点,客观题考数组初始化、越界,编程题考“一维数组模拟”“二维数组遍历”,尤其是“前缀和数组”“差分数组”,几乎每年省赛都考。

2.2.1 一维数组的3个竞赛高频用法

• 用法1:存储多个同类型数据(如存储100个学生的成绩,计算平均分);

◦ 例题:输入10个整数,求最大值和最小值的差。
#include <stdio.h>
int main() {
    int a[10], max, min;
    for (int i=0; i<10; i++) {
        scanf("%d", &a[i]);
    }
    max = min = a[0];
    for (int i=1; i<10; i++) {
        if (a[i] > max) max = a[i];
        if (a[i] < min) min = a[i];
    }
    printf("%d\n", max - min);
    return 0;
}
• 用法2:模拟“栈”或“队列”(竞赛中不考STL,用数组模拟更简单);

◦ 栈(先进后出):用top变量记录栈顶位置,push时top++,pop时top--;
int stack[100], top = 0;
stack[top++] = 1; // push 1
stack[top++] = 2; // push 2
top--; // pop 2
printf("%d\n", stack[top-1]); // 输出栈顶元素1
◦ 队列(先进先出):用front和rear变量,push时rear++,pop时front++;
int queue[100], front = 0, rear = 0;
queue[rear++] = 1; // push 1
queue[rear++] = 2; // push 2
front++; // pop 1
printf("%d\n", queue[front]); // 输出队首元素2
• 用法3:前缀和数组(快速计算区间和,省赛第6题高频考点);

◦ 定义:前缀和数组s[i] = a[1] + a[2] + ... + a[i](注意数组从1开始存储,方便计算);

◦ 公式:区间[l, r]的和 = s[r] - s[l-1];

◦ 例题:输入10个整数,求第3到第7个整数的和。
#include <stdio.h>
int main() {
    int a[11] = {0}, s[11] = {0}; // 从1开始存储,a[0]不用
    for (int i=1; i<=10; i++) {
        scanf("%d", &a[i]);
        s[i] = s[i-1] + a[i]; // 计算前缀和
    }
    printf("%d\n", s[7] - s[2]); // 第3-7个和 = s[7]-s[2]
    return 0;
}
2.2.2 二维数组的2个必考场景

• 场景1:矩阵遍历(如求二维数组的最大值、行和列的和);

◦ 例题:输入3行3列的矩阵,求所有元素的和。
#include <stdio.h>
int main() {
    int a[3][3], sum = 0;
    for (int i=0; i<3; i++) { // 行遍历
        for (int j=0; j<3; j++) { // 列遍历
            scanf("%d", &a[i][j]);
            sum += a[i][j];
        }
    }
    printf("%d\n", sum);
    return 0;
}
• 场景2:动态规划的“状态存储”(如“最长公共子序列”“背包问题”,国赛高频考点);

◦ 核心:用dp[i][j]表示“前i个物品、容量为j时的最大价值”,二维数组存储状态转移的中间结果。

2.2.3 数组越界:竞赛中最隐蔽的“丢分杀手”

• 问题:C语言不检查数组越界,越界后程序可能“看似正确”,但提交后会WA(Wrong Answer)或RE(Runtime Error);

• 避坑指南:

1. 定义数组时,比题目要求大2-3个空间(如题目要求存储100个元素,定义int a[105]; 避免因计算错误导致越界);

2. 遍历数组时,严格检查循环条件(如for (int i=0; i<n; i++),避免写成i<=n);

3. 二维数组初始化时,若题目没说大小,用“静态数组”(如int a[1005][1005]; 栈空间足够,避免用动态内存分配malloc,容易出错)。

2.3 函数:竞赛中“代码复用”的核心(附模板函数)

函数在竞赛中的作用不是“代码规范”,而是“减少重复代码、快速调用模板”,尤其是递归函数和自定义工具函数,必须熟练掌握。

2.3.1 递归函数:解决“重复子问题”的利器(省赛第7题高频)

• 核心思想:把大问题拆成和大问题结构相同的小问题,直到小问题可以直接解决(递归终止条件);

• 3个必背递归模板:

1. 阶乘计算(n! = n * (n-1)! ,终止条件n=1时返回1):
int factorial(int n) {
    if (n == 1) return 1; // 终止条件
    return n * factorial(n-1); // 递归调用
}
2. 斐波那契数列(f(n) = f(n-1) + f(n-2) ,终止条件f(1)=1, f(2)=1):
int fib(int n) {
    if (n == 1 || n == 2) return 1; // 终止条件
    return fib(n-1) + fib(n-2); // 递归调用
}
3. 最大公约数(gcd(a,b) = gcd(b, a%b) ,终止条件b=0时返回a):
int gcd(int a, int b) {
    if (b == 0) return a; // 终止条件
    return gcd(b, a%b); // 递归调用
}
• 递归避坑:避免重复计算(如斐波那契数列递归会重复计算f(3)、f(4),可用“记忆化搜索”优化,即把计算过的结果存在数组里,下次直接用):
int memo[100] = {0}; // 存储计算过的斐波那契数
int fib_memo(int n) {
    if (n == 1 || n == 2) return 1;
    if (memo[n] != 0) return memo[n]; // 已计算过,直接返回
    memo[n] = fib_memo(n-1) + fib_memo(n-2); // 计算后存入memo
    return memo[n];
}
2.3.2 自定义工具函数:竞赛中“节省时间”的关键

把竞赛中常用的功能写成函数,考试时直接调用,能节省大量时间,必写的3个工具函数:

1. 质数判断函数(判断一个数是否为质数,省赛填空/编程题必考):
// 判断x是否为质数,是返回1,否返回0
int is_prime(int x) {
    if (x <= 1) return 0; // 1及以下不是质数
    for (int i=2; i*i <= x; i++) { // 优化:只需遍历到sqrt(x)
        if (x % i == 0) return 0;
    }
    return 1;
}
2. 排序函数(冒泡排序,简单易写,适合竞赛):
// 对数组a的前n个元素从小到大排序
void bubble_sort(int a[], int n) {
    for (int i=0; i<n-1; i++) {
        for (int j=0; j<n-1-i; j++) {
            if (a[j] > a[j+1]) {
                // 交换a[j]和a[j+1]
                int temp = a[j];
                a[j] = a[j+1];
                a[j+1] = temp;
            }
        }
    }
}
3. 进制转换函数(十进制转二进制/八进制,客观题必考):
// 十进制x转二进制,存入数组b(返回二进制的位数)
int dec_to_bin(int x, int b[]) {
    int len = 0;
    while (x > 0) {
        b[len++] = x % 2; // 取余数,存到数组
        x = x / 2;
    }
    // 数组中存储的是二进制的逆序,如需正序,可反转数组
    for (int i=0; i<len/2; i++) {
        int temp = b[i];
        b[i] = b[len-1-i];
        b[len-1-i] = temp;
    }
    return len;
}
2.4 指针:竞赛中“进阶题”的敲门砖(国赛重点)

指针在省赛中考得不多(仅客观题1-2题,编程题几乎不考),但国赛中会在第9-10题出现(如链表、字符串处理),核心掌握“指针与数组的关系”和“指针作为函数参数”。

2.4.1 指针与数组的核心关系:a[i] = *(a+i)

数组名a本质是“指向数组首元素的指针”,所以:

• a[0] 等价于 *(a+0) (指向首元素,取值);

• a[1] 等价于 *(a+1) (指向首元素后第1个元素,取值);

• 遍历数组时,可用指针代替下标(代码更简洁,速度更快):
#include <stdio.h>
int main() {
    int a[5] = {1,2,3,4,5};
    int *p = a; // p指向数组首元素
    for (int i=0; i<5; i++) {
        printf("%d ", *(p+i)); // 等价于a[i]
    }
    return 0;
}
2.4.2 指针作为函数参数:实现“函数修改外部变量”

C语言函数参数是“值传递”,若想在函数中修改外部变量,必须用指针:

• 例题:用函数交换两个整数的值。
#include <stdio.h>
// 用指针接收变量地址,修改外部变量
void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}
int main() {
    int a=1, b=2;
    swap(&a, &b); // 传入变量地址
    printf("a=%d, b=%d\n", a, b); // 输出a=2, b=1
    return 0;
}
2.4.3 指针避坑:3个新手常犯的错误

• 错误1:未初始化指针就使用(如int *p; *p = 1; 野指针,程序会崩溃);

◦ 解决:要么让指针指向已定义的变量(int a; int *p=&a;),要么用malloc分配内存(int p = (int)malloc(sizeof(int)););

• 错误2:指针越界(如int a[5]; int *p=a; *(p+10)=1; 越界访问,程序可能WA);

◦ 解决:严格控制指针访问范围,避免超出数组长度;

• 错误3:混淆“指针变量”和“指针指向的值”(如printf("%d", p); 输出的是指针地址,不是值,应该用printf("%d", *p););

◦ 解决:记住“*p”是“取值”,“p”是“地址”。

第三部分:算法实战篇——蓝桥杯高频算法全解析(附真题代码)

3.1 枚举算法:蓝桥杯“性价比最高”的算法(省赛前6题必用)

枚举(暴力)是蓝桥杯最核心、最容易掌握的算法,80%的省赛题可以用枚举解决,甚至部分国赛题(第7-8题)也能靠枚举拿到一半以上的分数。其核心思想是“遍历所有可能的情况,判断是否符合条件”,优点是简单易写,缺点是效率低,但蓝桥杯的数据范围通常不大,枚举完全能过。

3.1.1 枚举的3个核心步骤

1. 确定“枚举范围”:明确要遍历的变量和范围(如求1-100的质数,范围是1到100);

2. 确定“判断条件”:明确符合题意的条件(如判断一个数是否为质数,条件是除了1和自身,不能被其他数整除);

3. 优化枚举效率(可选):减少不必要的遍历(如判断质数时,遍历到sqrt(x)而不是x-1)。

3.1.2 枚举算法的5道高频真题解析(省赛填空/编程题)

真题1:蓝桥杯2023年省赛B组填空第2题——《幸运数字》

• 题目:小明认为“幸运数字”是只包含数字6和8的数,如6、8、66、68。请计算1到10000中,共有多少个幸运数字?

• 分析:枚举1到10000的所有数,判断每个数的每一位是否只有6和8;

• 代码:
#include <stdio.h>
int main() {
    int count = 0;
    for (int i=1; i<=10000; i++) { // 枚举范围:1-10000
        int x = i;
        int flag = 1; // 1表示是幸运数字,0表示不是
        while (x > 0) {
            int digit = x % 10; // 取最后一位
            if (digit != 6 && digit != 8) {
                flag = 0;
                break;
            }
            x = x / 10; // 去掉最后一位
        }
        if (flag == 1) {
            count++;
        }
    }
    printf("%d\n", count); // 答案:30
    return 0;
}
真题2:蓝桥杯2022年省赛B组编程第3题——《序列求和》

• 题目:输入两个整数a和b,求a + aa + aaa + ... + aa...a(共b项)的和。例如a=2,b=3,和为2+22+222=246。

• 分析:枚举b项,每一项是前一项10 + a(如第1项2,第2项210+2=22,第3项22*10+2=222),累加求和;

• 代码:
#include <stdio.h>
int main() {
    int a, b;
    scanf("%d%d", &a, &b);
    long long sum = 0; // 注意用long long,避免溢出
    long long term = 0; // 存储当前项
    for (int i=0; i<b; i++) { // 枚举b项
        term = term * 10 + a;
        sum += term;
    }
    printf("%lld\n", sum);
    return 0;
}
真题3:蓝桥杯2021年省赛B组填空第4题——《日期问题》

• 题目:小明忘记了自己的生日,只记得是“2020年的某一天,星期几是当月的第3个星期五”。请计算2020年共有多少天满足这个条件?

• 分析:枚举2020年的每个月,计算每个月的第3个星期五是几号(2020年1月1日是星期三);

• 思路:

1. 2020年是闰年,1-12月的天数分别为:31,29,31,30,31,30,31,31,30,31,30,31;

2. 每周7天,1月1日是星期三,那么1月的第一个星期五是1月3日(1月1日周三,1月2日周四,1月3日周五);

3. 每个月的第n个星期五 = 第一个星期五 + 7*(n-1);

• 代码:
#include <stdio.h>
int main() {
    // 2020年1-12月的天数
    int days[] = {31,29,31,30,31,30,31,31,30,31,30,31};
    int week = 3; // 1月1日是星期三(0=周日,1=周一,...,6=周六)
    int count = 0;
    for (int month=0; month<12; month++) { // 枚举12个月
        // 找当月第一个星期五(week为3=周三,第一个周五是3 + (5-3) = 5?不对,重新算)
        // 正确方法:第一个星期五的日期 = (5 - week + 7) % 7 + 1(若结果为1则是7号)
        int first_fri = (5 - week + 7) % 7;
        if (first_fri == 0) first_fri = 7; // 若为0,说明第一个周五是7号
        // 第三个星期五 = 第一个周五 + 14(7*2)
        int third_fri = first_fri + 14;
        if (third_fri <= days[month]) { // 确保第三个周五在当月
            count++;
        }
        // 更新下个月1号的星期(当月天数 %7,加到week上)
        week = (week + days[month]) % 7;
    }
    printf("%d\n", count); // 答案:12
    return 0;
}
真题4:蓝桥杯2020年省赛B组编程第5题——《质数个数》

• 题目:计算1到20200408之间共有多少个质数。

• 分析:枚举1到20200408的所有数,用“质数判断函数”判断,累加计数;

• 优化:用“埃氏筛法”(比枚举判断快10倍以上,适合大范围质数计数);

• 埃氏筛法思路:

1. 定义一个布尔数组is_prime,is_prime[i]表示i是否为质数;

2. 初始化为1(默认都是质数),然后把is_prime[0]和is_prime[1]设为0;

3. 从2开始,把每个质数的倍数都设为0(不是质数);

• 代码:
#include <stdio.h>
#include <string.h>
#define MAX 20200408
int main() {
    int is_prime[MAX+1];
    memset(is_prime, 1, sizeof(is_prime)); // 初始化为1
    is_prime[0] = is_prime[1] = 0; // 0和1不是质数
    for (int i=2; i*i <= MAX; i++) { // 遍历到sqrt(MAX)
        if (is_prime[i] == 1) { // 若i是质数,把i的倍数设为0
            for (int j=i*i; j<=MAX; j+=i) { // 优化:从i*i开始,避免重复标记
                is_prime[j] = 0;
            }
        }
    }
    // 统计质数个数
    int count = 0;
    for (int i=2; i<=MAX; i++) {
        if (is_prime[i] == 1) {
            count++;
        }
    }
    printf("%d\n", count); // 答案:1283060
    return 0;
}
真题5:蓝桥杯2019年省赛B组填空第3题——《平方和》

• 题目:计算1到2019中,所有满足“每个数字的平方和等于100”的数的和。例如:137,1²+3²+7²=1+9+49=59≠100;148,1²+4²+8²=1+16+64=81≠100;286,2²+8²+6²=4+64+36=100,符合条件。

• 分析:枚举1到2019的所有数,计算每个数的每一位的平方和,判断是否等于100,若符合则累加;

• 代码:
#include <stdio.h>
int main() {
    long long sum = 0;
    for (int i=1; i<=2019; i++) { // 枚举范围:1-2019
        int x = i;
        int square_sum = 0;
        while (x > 0) {
            int digit = x % 10;
            square_sum += digit * digit;
            x = x / 10;
        }
        if (square_sum == 100) {
            sum += i;
        }
    }
    printf("%lld\n", sum); // 答案:20319
    return 0;
}
3.1.3 枚举算法的3个优化技巧(避免超时)

1. 缩小枚举范围:比如求“两个数a和b,a+b=100,a*b最大”,不需要枚举1-100,只需枚举1-50(因为a和b对称);

2. 提前终止循环:比如判断质数时,一旦找到能整除的数,立即终止循环(如真题1中的flag=0后break);

3. 用数学公式代替枚举:比如求“1-100的和”,用公式n*(n+1)/2,比枚举累加快100倍。

3.2 递归算法:解决“嵌套问题”的核心(省赛第7题高频)

递归在蓝桥杯中的考点主要是“斐波那契数列”“阶乘”“深度优先搜索(DFS)”,尤其是DFS,几乎每年省赛第7-8题都会考(如迷宫问题、组合问题)。

3.2.1 递归的2个核心应用场景

1. 问题具有“自相似性”:如斐波那契数列、阶乘,大问题可以拆成小问题;

2. 问题需要“深度遍历”:如迷宫问题(从起点出发,探索所有可能的路径,直到找到终点)、组合问题(从n个数中选k个,枚举所有组合)。

3.2.2 递归算法的3道高频真题解析

真题1:蓝桥杯2023年省赛B组编程第7题——《迷宫问题》

• 题目:给定一个5x5的迷宫,0表示通路,1表示障碍。起点是(0,0),终点是(4,4),只能上下左右移动,求从起点到终点的最短路径长度(步数)。

• 迷宫如下:
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
• 分析:用DFS或BFS(广度优先搜索),BFS更适合求最短路径(DFS需要遍历所有路径,BFS找到的第一条路径就是最短的);

• BFS思路:

1. 用队列存储当前位置的坐标(x,y)和步数step;

2. 每次取出队首元素,判断是否是终点,若是则返回step;

3. 否则,向上下左右四个方向探索,若该方向未访问且是通路,加入队列,并标记为已访问;

• 代码:
#include <stdio.h>
#define N 5
// 迷宫地图
int maze[N][N] = {
    {0,1,0,0,0},
    {0,1,0,1,0},
    {0,0,0,0,0},
    {0,1,1,1,0},
    {0,0,0,1,0}
};
// 访问标记(0未访问,1已访问)
int visited[N][N] = {0};
// 上下左右四个方向(dx, dy)
int dir[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};
// 队列:存储x, y, step
typedef struct {
    int x, y, step;
} Queue;
Queue q[N*N]; // 队列大小足够(5x5=25)
int front = 0, rear = 0;

int bfs() {
    // 起点入队
    q[rear++] = (Queue){0, 0, 0};
    visited[0][0] = 1;
    while (front < rear) { // 队列不为空
        Queue curr = q[front++]; // 取出队首
        // 判断是否到达终点
        if (curr.x == N-1 && curr.y == N-1) {
            return curr.step;
        }
        // 探索四个方向
        for (int i=0; i<4; i++) {
            int nx = curr.x + dir[i][0];
            int ny = curr.y + dir[i][1];
            // 判断nx和ny是否在迷宫范围内,且未访问,且是通路
            if (nx >=0 && nx < N && ny >=0 && ny < N && !visited[nx][ny] && maze[nx][ny] == 0) {
                visited[nx][ny] = 1; // 标记为已访问
                q[rear++] = (Queue){nx, ny, curr.step + 1}; // 入队
            }
        }
    }
    return -1; // 没有路径(本题有路径,不会执行)
}

int main() {
    printf("%d\n", bfs()); // 答案:8
    return 0;
}
真题2:蓝桥杯2022年省赛B组编程第8题——《组合问题》

• 题目:从1到n中选k个不同的数,求所有组合的和。例如n=3,k=2,组合有(1,2)、(1,3)、(2,3),和为(1+2)+(1+3)+(2+3)=12。

• 分析:用递归DFS枚举所有组合,避免重复(如(1,2)和(2,1)是同一个组合,所以规定选数时“后一个数比前一个大”);

• 思路:

1. 定义递归函数dfs(start, count, sum),start表示当前选数的起始位置,count表示已选的个数,sum表示已选数的和;

2. 终止条件:count == k,把sum加到总结果中;

3. 递归过程:从start到n,选一个数,然后递归选下一个数(start设为当前数+1,count+1,sum+当前数);

• 代码:
#include <stdio.h>
int n, k;
long long total = 0; // 存储总结果

// start:当前选数的起始位置,count:已选个数,sum:已选数的和
void dfs(int start, int count, int sum) {
    if (count == k) { // 选够k个,加到总结果
        total += sum;
        return;
    }
    // 从start到n,选下一个数(避免重复,start递增)
    for (int i=start; i<=n; i++) {
        dfs(i+1, count+1, sum + i);
    }
}

int main() {
    scanf("%d%d", &n, &k);
    dfs(1, 0, 0); // 初始:从1开始选,已选0个,和为0
    printf("%lld\n", total);
    return 0;
}
真题3:蓝桥杯2021年省赛B组编程第7题——《斐波那契数列求和》

• 题目:求斐波那契数列的前n项中,所有偶数项的和。例如n=10,斐波那契数列前10项是1,1,2,3,5,8,13,21,34,55,偶数项是2,8,34,和为44。

• 分析:用递归或迭代求斐波那契数列,判断每一项是否为偶数,累加;

• 注意:递归求斐波那契数列效率低,n大时会超时,所以用迭代;

• 代码:
#include <stdio.h>
int main() {
    int n;
    scanf("%d", &n);
    if (n < 3) { // 前2项都是1,没有偶数
        printf("0\n");
        return 0;
    }
    long long a = 1, b = 1, c; // a=f(1), b=f(2)
    long long sum = 0;
    for (int i=3; i<=n; i++) {
        c = a + b; // f(i) = f(i-1) + f(i-2)
        if (c % 2 == 0) {
            sum += c;
        }
        a = b;
        b = c;
    }
    printf("%lld\n", sum);
    return 0;
}
3.2.3 递归算法的2个避坑技巧

1. 设置终止条件:避免递归无限循环(如斐波那契数列的终止条件是n=1或n=2);

2. 避免重复计算:用“记忆化搜索”(如把计算过的斐波那契数存在数组里,下次直接用),否则n=40时递归会超时。

3.3 动态规划:国赛“分水岭”算法(国赛第9-10题必考)

动态规划(DP)是蓝桥杯国赛的核心考点,也是区分省一和国一的关键。其核心思想是“把大问题拆成小问题,存储小问题的解,避免重复计算”,难点在于“状态定义”和“状态转移方程”。

3.3.1 动态规划的3个核心步骤

1. 定义状态:用dp[i]或dp[i][j]表示“前i个元素/前i个物品/第i步”的最优解(如dp[i]表示“前i个斐波那契数的和”);

2. 确定状态转移方程:找到dp[i]和dp[i-1]、dp[i-2]等的关系(如斐波那契数列的dp[i] = dp[i-1] + dp[i-2]);

3. 初始化状态:设置dp数组的初始值(如斐波那契数列的dp[1] = 1,dp[2] = 1)。

3.3.2 动态规划的2道高频真题解析(国赛重点)

真题1:蓝桥杯2023年国赛B组编程第9题——《01背包问题》

• 题目:有n个物品,每个物品有重量w[i]和价值v[i],背包容量为C,每个物品只能选一次,求背包能装下的最大价值。

• 分析:01背包是动态规划的经典问题,状态定义为dp[i][j]表示“前i个物品,背包容量为j时的最大价值”;

• 状态转移方程:

◦ 不选第i个物品:dp[i][j] = dp[i-1][j];

◦ 选第i个物品(前提是j >= w[i]):dp[i][j] = dp[i-1][j - w[i]] + v[i];

◦ 所以dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]);

• 初始化:dp[0][j] = 0(前0个物品,价值为0),dp[i][0] = 0(背包容量为0,价值为0);

• 代码(空间优化:用一维数组,从后往前遍历):
#include <stdio.h>
#include <string.h>
#define MAX_C 1000
int dp[MAX_C+1]; // 一维dp数组,dp[j]表示容量为j时的最大价值

int main() {
    int n, C;
    scanf("%d%d", &n, &C);
    int w[n+1], v[n+1];
    for (int i=1; i<=n; i++) {
        scanf("%d%d", &w[i], &v[i]);
    }
    memset(dp, 0, sizeof(dp)); // 初始化dp为0
    for (int i=1; i<=n; i++) { // 遍历每个物品
        // 从后往前遍历容量,避免重复选同一个物品
        for (int j=C; j>=w[i]; j--) {
            if (dp[j - w[i]] + v[i] > dp[j]) {
                dp[j] = dp[j - w[i]] + v[i];
            }
        }
    }
    printf("%d\n", dp[C]); // 容量为C时的最大价值
    return 0;
}
真题2:蓝桥杯2022年国赛B组编程第10题——《最长公共子序列》

• 题目:输入两个字符串s和t,求它们的最长公共子序列(LCS)的长度。例如s="ABCBDAB",t="BDCAB",LCS是"BCAB"或"BDAB",长度为4。

• 分析:LCS是动态规划的经典问题,状态定义为dp[i][j]表示“s的前i个字符和t的前j个字符的LCS长度”;

• 状态转移方程:

◦ 若s[i-1] == t[j-1](字符串从0开始,所以i-1是s的第i个字符):dp[i][j] = dp[i-1][j-1] + 1;

◦ 若s[i-1] != t[j-1]:dp[i][j] = max(dp[i-1][j], dp[i][j-1]);

• 初始化:dp[0][j] = 0(s的前0个字符,LCS长度为0),dp[i][0] = 0(t的前0个字符,LCS长度为0);

• 代码:
#include <stdio.h>
#include <string.h>
#define MAX_LEN 1000
int dp[MAX_LEN+1][MAX_LEN+1]; // dp[i][j]表示s前i个和t前j个的LCS长度

int max(int a, int b) {
    return a > b ? a : b;
}

int main() {
    char s[MAX_LEN], t[MAX_LEN];
    scanf("%s%s", s, t);
    int len_s = strlen(s);
    int len_t = strlen(t);
    // 初始化dp[0][j]和dp[i][0]为0(数组默认初始化为0,可省略)
    for (int i=1; i<=len_s; i++) {
        for (int j=1; j<=len_t; j++) {
            if (s[i-1] == t[j-1]) {
                dp[i][j] = dp[i-1][j-1] + 1;
            } else {
                dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
            }
        }
    }
    printf("%d\n", dp[len_s][len_t]);
    return 0;
}
3.3.3 动态规划的2个学习技巧

1. 从简单题入手:先学“斐波那契数列”“爬楼梯”等简单DP问题,理解状态定义和转移方程,再学“01背包”“LCS”等复杂问题;

2. 画图辅助理解:比如LCS问题,画一个二维表格,手动计算dp[i][j]的值,理解状态转移的过程。

第四部分:真题冲刺篇——近5年省赛真题拆解(附得分技巧)

4.1 蓝桥杯省赛真题的“题型规律”(2020-2024)

通过分析近5年蓝桥杯C语言省赛B组真题,发现题型和考点高度固定,可总结为“5+5”模式:

• 前5题(客观题,50分):

1. 第1题:基础语法(数组初始化、指针地址、函数参数);

2. 第2题:简单计算(进制转换、日期计算、质数判断);

3. 第3题:枚举填空(如幸运数字、平方和,计算结果填空);

4. 第4题:字符串处理(字符串长度、字符替换、子串查找);

5. 第5题:算法选择(判断用枚举、递归还是动态规划,选择题);

• 后5题(编程题,50分):
6. 第6题:枚举应用题(如序列求和、数组统计,代码量10-20行);
7. 第7题:递归/DFS(如迷宫问题、组合问题,代码量20-30行);
8. 第8题:数学计算(如最大公约数、最小公倍数、排列组合,代码量20-30行);
9. 第9题:动态规划入门(如爬楼梯、简单背包,代码量30-40行);
10. 第10题:综合题(如复杂动态规划、图论,代码量40-50行,国赛难度)。

4.2 2024年蓝桥杯省赛B组真题全解析(附得分技巧)

2024年蓝桥杯省赛B组真题(回忆版)

1. 选择题1:以下代码的输出结果是(  )
#include <stdio.h>
int main() {
    int a[5] = {1,2,3,4,5};
    int *p = a + 2;
    printf("%d\n", *(p+1));
    return 0;
}
A. 3  B. 4  C. 5  D. 6
◦ 解析:a是数组首地址,a+2指向a[2](值为3),p+1指向a[3](值为4),所以输出4;

◦ 得分技巧:记住“指针+整数”是“地址偏移”,偏移量是“整数*数据类型大小”(int是4字节,所以a+2偏移8字节,指向a[2])。

2. 填空题1:计算1到1000中,所有能被3或5整除的数的和。

◦ 解析:枚举1到1000,判断i%3==0或i%5==0,累加;

◦ 代码:
#include <stdio.h>
int main() {
    int sum = 0;
    for (int i=1; i<=1000; i++) {
        if (i%3 == 0 || i%5 == 0) {
            sum += i;
        }
    }
    printf("%d\n", sum); // 答案:234168
}
◦ 得分技巧:注意“或”是||,不是|(|是按位或,会出错)。

3. 编程题1(第6题):输入n,输出n行的“菱形图案”。例如n=5,输出:
  *
 ***
*****
 ***
  *
◦ 解析:分上半部分(3行)和下半部分(2行),上半部分每行的空格数递减,星号数递增;下半部分相反;

◦ 代码:
#include <stdio.h>
int main() {
    int n;
    scanf("%d", &n);
    int mid = n / 2; // 中间行的位置(n=5,mid=2)
    // 上半部分(包括中间行)
    for (int i=0; i<=mid; i++) {
        // 输出空格:mid - i个
        for (int j=0; j<mid - i; j++) {
            printf(" ");
        }
        // 输出星号:2*i +

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值