第十五届蓝桥杯C/C++B组省赛真题讲解(分享去年比赛的一些真实感受)

试题A------握手问题

一、解题思路

直接用高中学的排列组合思路

二、代码示例

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int fun(int n)
{
	int sum=0;
	for(int i=0;i<n;i++)
	{
		for(int j=i+1;j<n;j++)sum++;	
	}	
	return sum;
} 
int main()
{
	cout<<fun(50)-fun(7);
}

三、感悟:

这是一个签到题,都不需要编程都可以做出来

试题B------小球反弹

一、解题思路

大家可以先思考一下,因为题目要求是从左上角射出到第二次到达左上角,在x方向上,是不是一定走了偶数个343720长度,在y方向上,是不是一定也走了偶数个233333长度,设走了i个343720长度,j个233333长度,那x方向上是不是一共走了343720*i长度,y方向上一共走了233333*j长度,斜率又已知,那x方向走的长度比上y方向走的长度是不是就是dx/dy,遍历i,j,就可以求出来了

如果还是不太理解可以看一下下图:

假设小球经过四次撞击就可以回到原点(因为我是对称画的,实际上没有右边这一部分,但是撞击后走的路程是相同的,因为斜率是一定的,路程一样,那在x和y上面走的路程其实也是相同的)所以它最后在x上面走的路程和y上面走的路程是成一定比例的。

二、代码示例

cpp 复制代码
#include<bits/stdc++.h> 
using namespace std;
typedef long long int ll;
int main()
{
	//因为i,j一定是偶数,每次加2就行了 
	for(ll i=2;i<=10000;i+=2)
	{
		for(ll j=2;j<=10000;j+=2)
		{
			if(i*343720*17==j*233333*15)
			{
				cout<<i<<" "<<j<<endl; 
				printf("%.2lf",sqrt((i*343720)*(i*343720)+(j*233333)*(j*233333)));
				return 0;
				
			}
		}
	}
}

三、感悟

这题我当时思考了10来分钟一点没思路,后来全部都写完后,又来思考了半个小时,还是想不出来,就直接放弃了,后来看了大家的题解思路才知道是自己的物理学太菜了,大家在赛场上一定要有所取舍,填空题都是5分,如果不会就不要浪费太多时间。

C------好数

题目链接:https://www.lanqiao.cn/problems/19709/learning/

我在第十五届蓝桥杯C/C++B组国赛真题讲解(分享去年比赛的一些真实感受)-CSDN博客也讲过

一、解题思路

本题的目标是计算从 1 到给定正整数 N 范围内 "好数" 的数量。"好数" 的定义为:按从低位到高位的顺序,奇数位(个位、百位、万位......)上的数字是奇数,偶数位(十位、千位、十万位......)上的数字是偶数。

暴力思路的核心在于遍历从 1 到 N 的每一个整数,针对每个整数,逐一检查其每一位数字是否符合 "好数" 的定义。若符合,则将 "好数" 的计数加 1;若不符合,则继续检查下一个整数。最后,计数的结果即为从 1 到 N 中 "好数" 的总数。

二、代码展示

cpp 复制代码
#include<bits/stdc++.h>//库函数,万能头,记住就好 
using namespace std;//模板记 
//fun(int i)函数用来判断传进来的参数i是否是好数,是返回true,否则返回false 
bool fun(int i)
{
	//count用来记录当前要判断的是位数 
	int count=1;
	//若i等于0则循环结束 
	while(i)
	{
		//若count为奇数位,则count%2=1 
		if(count%2)
		{
			//a%10取当前最后一位数字,再对2求余
			//奇数位需要是奇数,若是偶数,则当前位不符合 
			if(i%10%2==0)return false;
		}
		//如果为偶数位 
		else
		{
			//偶数位需要是偶数,若是奇数,则当前位不符合
			if(i%10%2==1)return false;
		}
		//每次位数+1 
		count++;
		//i每次要舍掉最后一位 
		i/=10;
	}
	//若每一位都判断完成后都没有return false,说明此数是好数 
	return true;
}
int main()
{
	int n;
	cin>>n;
	int sum=0;
	for(int i=1;i<=n;i++)if(fun(i))sum++;
	cout<<sum;
	return 0;
}

三、感悟

纯暴力,签到题

D------R格式

题目链接:https://www.lanqiao.cn/problems/19710/learning/

一、解题思路

本题要求根据给定的转换参数 n ,将浮点数 d 按照特定规则转换为 R 格式整数 。规则是先将浮点数乘以 2^n ,再四舍五入到最接近的整数。解题的主要步骤就是实现这个乘法和四舍五入操作,但是这个乘法要用高精度,用普通的long long int会超。

二、代码展示

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

// 函数fun用于将字符串表示的数乘以2
string fun(string s)
{
    int n = s.size();
    int jinwei = 0;  // 用于记录进位
    for (int i = 0; i < n; i++)
    {
        if (s[i] == '.') continue;  // 遇到小数点跳过
        int temp = int(s[i] - '0');  // 将字符形式的数字转换为整型
        int neww = temp * 2 + jinwei;  // 当前位乘以2并加上进位
        jinwei = neww / 10;  // 计算新的进位
        neww %= 10;  // 取个位作为当前位新的值
        s[i] = char(neww + '0');  // 将新值转换回字符形式存回原字符串
    }
    if (jinwei > 0) s += char(jinwei + '0');  // 如果最后还有进位,添加到字符串末尾
    return s;
}

// 函数fun2用于对字符串表示的数进行四舍五入
string fun2(string s)
{
    reverse(s.begin(), s.end());  // 反转字符串,方便从低位开始处理
    int i = 0;
    while (s[i] != '.') i++;  // 找到小数点位置
    i++;  // 移动到小数点后一位
    int jinwei = 1;  // 初始进位为1,模拟进位操作
    while (jinwei)
    {
        int temp = s[i] - '0' + 1;  // 当前位数字加1(准备进位)
        jinwei = temp / 10;  // 计算新的进位
        temp %= 10;  // 取个位作为当前位新的值
        s[i] = temp + '0';  // 将新值转换回字符形式存回原字符串
        i++;  // 处理下一位
    }
    reverse(s.begin(), s.end());  // 再反转回原顺序
    return s;
}

int main()
{
    int n;
    string s;
    cin >> n >> s;  // 输入转换参数n和浮点数s(以字符串形式存储)
    reverse(s.begin(), s.end());  // 反转字符串,方便后续处理
    while (n--)
    {
        s = fun(s);  // 循环n次,每次将字符串表示的数乘以2
    }
    int count = 0;
    while (s[count] == '0' || s[count] == '.')
    {
        count++;  // 跳过前导0和小数点
    }
    reverse(s.begin(), s.end());  // 再次反转回原顺序
    int nLen = s.size() - count;  // 得到有效数字部分的长度
    int sign = 0;
    for (int i = 0; i < nLen; i++)
    {
        if (s[i] == '.') sign = int(s[i + 1] - '0');  // 找到小数点,记录小数点后一位数字
    }
    if (sign == 0)
    {
        for (int i = 0; i < nLen; i++) cout << s[i];  // 小数点后一位是0,直接输出有效数字部分
    }
    else
    {
        if (sign >= 5)
        {
            s = fun2(s);  // 小数点后一位大于等于5,进行四舍五入
            for (int i = 0; i < nLen; i++)
            {
                if (s[i] == '.') break;  // 遇到小数点停止输出
                else cout << s[i];  // 输出四舍五入后的整数部分
            }
        }
        else
        {
            for (int i = 0; i < nLen; i++)
            {
                if (s[i] == '.') break;  // 小数点后一位小于5,直接输出整数部分
                else cout << s[i];
            }
        }
    }
    return 0;
}

注意:我的代码不知道为什么只能过90%的样例,可能还有10%的情况我没有考虑到,求大神告知

三、感悟

这一题的话如果学了高精度的话并不难,如果没有学的话,可以直接用暴力,最好先用快速幂求出2^n,然后乘以d,四舍五入判断进行强制类型转换,判断是否加1就行

E------宝石组合

题目链接:https://www.lanqiao.cn/problems/19711/learning/

一、解题思路

(由于其他同学讲的特别好,所以我借鉴过来了)

二、代码示例

cpp 复制代码
#include<stdio.h>
const int h=1e5;
int main(){
    int n;
    scanf("%d",&n);
    int mp[h+1]={0};//初始化宝石闪亮度统计表
    for(int i=0;i<n;i++){
        int t;
        scanf("%d",&t);
        mp[t]++;//统计亮度为t的宝石数量
    }
    //这里我们另辟蹊径,直接枚举精美程度
    for(int i=h;i>=1;i--){//枚举精美程度i
        int ans=0,now=0;//ans表示寻找到了几个宝石,now表示现在数组有几个宝石
        int num[3];//初始化枚举到的宝石
        for(int j=i;j<=h;j+=i){//对于每个精美度i,我们都需要寻找闪亮度为i,2i,3i...的宝石并统计数量
            ans+=mp[j];//把寻找到的宝石数量统计起来
            for(int k=0;k<mp[j]&&now<3;k++){//把统计到的宝石放到数组
                num[now]=j;
                now++;
            }
            if(ans>=3){//如果找到了三个以上的宝石,说明存在三个宝石使其精美度为i
                for(int k=0;k<3;k++){
                    printf("%d ",num[k]);
                }//输出找到的三个宝石
                printf("\n");
                return 0;
            }
        }
    }
}

三、感悟

这题的做法也非常巧妙,我当时正赛的时候是暴力枚举,取三个宝石算s的,但是这题这样去枚举精美程度可能会更简单一些,时间复杂度少了很多,从O(N^3)降到了O(N^2),有点像国赛的立定跳远,去枚举遍历你所要求的那一个值。

F------数字接龙

题目链接:https://www.lanqiao.cn/problems/19712/learning/

我在https://blog.csdn.net/SUN19326410095/article/details/147070595也讲过

一、解题思路

本题可通过深度优先搜索(DFS)来求解。由于要在 N×N的棋盘上,从左上角(0, 0)出发找到满足特定规则到达右下角(N - 1, N - 1)的路径,DFS 适合这种在多种可能路径中进行探索的场景。游戏规则要求路径数字按0到(K - 1)循环,且每个格子仅经过一次、路径不交叉,所以在 DFS 过程中,从起始点开始,每到一个格子,需按 8 个方向(水平、垂直、对角线)去探索新格子。对于每个新格子,要判断是否在棋盘内,防止越界;检查是否已访问,保证每个格子只走一次;确认数字是否符合循环序列,确保路径数字规则正确;还要查看路径是否交叉,满足所有这些条件才能继续递归探索。持续此过程,要么找到符合规则的路径,若有多条则按字典序选取最小的输出,要么确定不存在路径时输出-1 。

二、代码展示

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
 
const int N = 11; // 定义棋盘的最大大小
int n, k; // n 为棋盘大小,k 为数字循环的范围
int board[N][N]; // 存储棋盘上的数字
int dx[8] = {-1, -1, 0, 1, 1, 1, 0, -1}; // 定义 8 个方向的 x 坐标偏移
int dy[8] = {0, 1, 1, 1, 0, -1, -1, -1}; // 定义 8 个方向的 y 坐标偏移
string path; // 存储路径的方向编号
bool visited[N][N]; // 标记棋盘上的格子是否被访问过
bool edge[N][N][N][N]; // 检查路径是否交叉
 
// 深度优先搜索函数,用于寻找路径
bool dfs(int x, int y) {
    // 如果到达右下角格子,检查路径长度是否为 n*n - 1(因为起点不计入路径)
    if (x == n - 1 && y == n - 1) {
        return path.size() == n * n - 1;
    }
    visited[x][y] = true; // 标记当前格子已访问
    for (int i = 0; i < 8; i++) { // 遍历 8 个方向
        int newX = x + dx[i];
        int newY = y + dy[i];
        // 检查目标格子是否越界、是否访问过、数字是否满足循环序列要求
        if (newX < 0 || newX >= n || newY < 0 || newY >= n) continue;
        if (visited[newX][newY]) continue;
        if (board[newX][newY] != (board[x][y] + 1) % k) continue;
        // 检查路径是否交叉(对于斜向移动,检查是否有反向的路径)
        if (edge[x][newY][newX][y] || edge[newX][y][x][newY]) continue;
 
        edge[x][y][newX][newY] = true; // 标记路径
        path += i + '0'; // 将方向编号加入路径
        if (dfs(newX, newY)) return true; // 递归搜索下一个格子
        path.pop_back(); // 回溯,移除路径中的最后一个方向
        edge[x][y][newX][newY] = false; // 回溯,取消路径标记
    }
    visited[x][y] = false; // 回溯,取消当前格子的访问标记
    return false; // 如果所有方向都无法到达终点,返回 false
}
 
int main() {
    cin >> n >> k; // 输入棋盘大小和数字循环范围
    for (int i = 0; i < n; i++) { // 读取棋盘上的数字
        for (int j = 0; j < n; j++) {
            cin >> board[i][j];
        }
    }
    // 从起点 (0, 0) 开始搜索路径
    if (!dfs(0, 0)) {
        cout << -1 << endl; // 如果没有找到路径,输出 -1
    } else {
        cout << path << endl; // 输出路径的方向编号序列
    }
    return 0;
}

三、感悟

dfs和bfs年年必考,最近几年dfs考的最多,像这种题目思路都很类似,很容易掌握

G------爬山

我在蓝桥杯C/C++5天逆袭省一之第二天------C++STL容器(增强版)-CSDN博客讲过

一、解题思路

二、代码展示

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
 
int main()
{
    int n, P, Q;
    long long ans = 0;
    // 定义一个大顶堆h,用于存储山的高度,方便每次取出最高的山
    priority_queue<int> h;
    // 读取山的数量n,第一种魔法的可用次数P,第二种魔法的可用次数Q
    cin >> n >> P >> Q;
    for (int i = 0; i < n; i++)
    {
        int hi;
        // 读取每座山的高度hi
        cin >> hi;
        // 将山的高度hi加入大顶堆h中
        h.push(hi);
    }
    // 当两种魔法还有可用次数时,循环进行操作
    while (P || Q)
    {
        // 取出堆顶元素,即当前最高的山的高度
        int first = h.top();
        h.pop();
        // 如果两种魔法都有可用次数
        if (P && Q)
        {
            // 比较两种魔法作用后的山的高度,选择能使山变得更矮的魔法
            if (sqrt(first) <= first / 2)
            {
                // 使用第一种魔法,将山的高度变为其平方根向下取整
                h.push(sqrt(first));
                // 第一种魔法可用次数减1
                P--;
            }
            else
            {
                // 使用第二种魔法,将山的高度变为其一半向下取整
                h.push(first / 2);
                // 第二种魔法可用次数减1
                Q--;
            }
        }
        // 如果只剩下第一种魔法有可用次数
        else if (P)
        {
            // 使用第一种魔法,将山的高度变为其平方根向下取整
            h.push(sqrt(first));
            // 第一种魔法可用次数减1
            P--;
        }
        // 如果只剩下第二种魔法有可用次数
        else if (Q)
        {
            // 使用第二种魔法,将山的高度变为其一半向下取整
            h.push(first / 2);
            // 第二种魔法可用次数减1
            Q--;
        }
    }
    // 遍历堆,将剩余山的高度累加起来,得到最终花费的体力值
    while (!h.empty())
    {
        ans += h.top();
        h.pop();
    }
    // 输出最优情况下需要花费的体力值
    cout << ans;
    return 0;
}

注意:这一题可能只能通过90%的样例,并不能通过全部样例

三、感悟

这一题也会有少量数据通过不了,例如2,1,3,35,36,欢迎大神来指教一下

H------拔河

我在蓝桥杯C/C++5天逆袭省一之第二天------C++STL容器(增强版)-CSDN博客讲过

一、解题思路

二、代码展示

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
// 定义数组的最大长度
const int N = 1e3+10;
// 存储前缀和数组
long long a[N];
// 同学的数量
int n;
// 用于存储所有可能的右区间的力量值之和
multiset<long long>s;
 
// 自定义函数,返回两个数中的较小值
long long minn(long long a,long long b){
    if(a<b) return a;
    else return b;
}
 
int main(){
    // 读取同学的数量
    cin>>n;
    // 读取每个同学的力量值,并构建前缀和数组
    for(int i = 1;i<=n;i++) {
        cin>>a[i];
        // 计算前缀和
        a[i]+=a[i-1]; 
    }
    // 枚举所有可能的右区间 [i, j],并将其力量值之和插入到 multiset 中
    for(int i = 1;i<=n;i++){
        for(int j = i;j<=n;j++){
            // 计算右区间 [i, j] 的力量值之和并插入到 multiset 中
            s.insert(a[j]-a[i-1]); 
        }
    }
    // 初始化最小差距为一个较大的值
    long long res = 1e9;
    // 枚举左区间的右端点 i
    for(int i = 1;i<n;i++){
        // 删除所有以 i 作为右区间起始点的情况,避免左区间和右区间重叠
        for(int j = i;j<=n;j++){
            // 计算以 i 为起始点的右区间 [i, j] 的力量值之和
            auto k = a[j] - a[i-1];
            // 从 multiset 中删除该力量值之和
            s.erase(s.find(k));
        }
        // 枚举左区间的左端点 j
        for(int j = 1;j<=i;j++){
            // 计算左区间 [j, i] 的力量值之和
            auto k = a[i] - a[j-1];
            // 在 multiset 中找到第一个大于等于 k 的元素
            auto p = s.lower_bound(k);
            // 如果找到了这样的元素,计算其与 k 的差值的绝对值,并更新最小差距
            if(p!=s.end()){
                res = minn(res,abs(*p-k));
            }
            // 如果 p 不是 multiset 的第一个元素,考虑其前一个元素与 k 的差值的绝对值,并更新最小差距
            if(p!=s.begin()){
                p--;
                res = minn(res,abs(*p-k));
            }
        }
    }
    // 输出最小差距
    cout<<res<<endl;
    return 0;
}

三、感悟:

有同学在后台询问我拔河这道题56-58行代码为什么还要判断,因为在前面lower_bound找到的只是在大于等于k的数里面最接近k的数,但我们实际上还需要找小于等于k的数里面最接近k的数,将它们差值的绝对值进行比较,因为我们要求的是俩区间差值的最小值

总结:

省赛来临之际,希望大家在学完相关算法知识后,一定要及时的去训练刷题,如果有什么疑问,可以在评论区留言,考前可以去一下我之前写的蓝桥杯C/C++实战经验分享-CSDN博客,里面分享了一些考试小技巧,希望大家取得好的比赛成绩!!

相关推荐
槐月杰5 小时前
入门到精通,C语言十大经典程序
c语言·数据结构·算法
a东方青6 小时前
[16届蓝桥杯 2025 c++省 B] 移动距离
c++·算法·蓝桥杯
学c++的一天7 小时前
蓝桥杯备战
算法·职场和发展·蓝桥杯
FreeLikeTheWind.7 小时前
Qt问题之 告别软件因系统默认中文输入法导致错误退出的烦恼
开发语言·c++·windows·经验分享·qt
爱看书的小沐8 小时前
【小沐学GIS】基于C++绘制三维数字地球Earth(QT5、OpenGL、GIS、卫星)第五期
c++·qt·opengl·imgui·地球·卫星·gis地球
小文数模8 小时前
2025年认证杯数学建模C题完整分析论文(共39页)(含模型、可运行代码)
c语言·开发语言·数学建模
天堂的恶魔9468 小时前
C++项目 —— 基于多设计模式下的同步&异步日志系统(1)
c++
杨某一辰9 小时前
库magnet使用指南
c++·多线程·
zero.cyx9 小时前
蓝桥杯 DFS
算法·蓝桥杯·深度优先
点纭9 小时前
C 语言 第八章 文件操作
c语言·开发语言