第十五届蓝桥杯C&C++大学B组

第十五届蓝桥杯C&C++大学B组

官方试题合集:蓝桥杯大赛历届真题_蓝桥杯 - 蓝桥云课

题目A:(握手问题)

思路:

这道题还是很简单的,7个人特殊就让他们排到最后握手,其余人两两握。

题解:

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>

using namespace std;

int main()
{
    int sum = 0;
    for (int n = 1; n <= 42; n++)
    {
        sum += n;
    }
    sum += 7 * 43;
    cout << sum;
    return 0;
}

题目B:(小球反弹)

思路:(暴力)

这道题没做过的还真很难做出来。

先看看暴力怎么写:

  1. 速率比是15:17,那路程比也肯定是15:17。
  2. 回到原点那么说明路程一定是长或宽的偶数倍。

题解:(暴力)

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main() {
	//暴力 
	for(long long i=2; i<=10000; x+=2)
		for(long long j=2; j<=100000; y+=2)
			if(15*233333*j==17*343720*i) {
				cout<<sqrt((343720*i)*(343720*i)+(233333*j)*(233333*j))//勾股定理;
				return 0;
			}
	return 0;
}

思路:(方程)

其实对于填空题直接暴力最省时间。

碰撞之后分速度大小不变,但是某个分速度会相反。

如果碰到下边,v下->v上,v右不变。相当于将长方形沿下边翻折。

如果碰到右边,相当于将长方形沿右边翻折。

我们知道路程比 dx : dy ,那么Y * dx == X * dy;令Y = y * 2 * a;X = x * 2 * b;

求a,b的最小值。

所以我们可以先求 X 和 Y 的最小公倍数。

题解:(方程)

cpp 复制代码
int main()
{
    double sum = 0;
    long long y = 233333;
    long long x = 343720;
    long long dy = 17;
    long long dx = 15;
    int a = 0;
    int b = 0;
    // y * 2 * dx * a==  x * 2 * dy * b;
    //  先求最大公约数
    long long g = __gcd(dx * y * 2, dy * x * 2);
    //  再求最小公倍数
    long long t = y * 2 * dx / g * x * 2 * dy;
    a = t / (y * 2 * dx);
    b = t / (x * 2 * dy);
    sum = sqrt((y * a * 2) * (y * a * 2) + (x * b * 2) * (x * b * 2));
    cout << fixed << setprecision(2) << sum;
    return 0;
}

LCM(a,b) = a*b / GCD(a,b)

最小公倍数 = 两个数的乘积 / 最大公约数

题目C:(好数)

思路:

没啥技巧,挨个算呗

题解:

cpp 复制代码
int main()
{
    int N;
    cin >> N;
    int count = 0;
    for (int i = 1; i <= N; i++)
    {
        int n = i;
        for (int j = 1; n > 0; j++, n /= 10)
        {
            if (j % 2 != (n % 10) % 2)
            {
                break;
            }
        }
        if (n == 0)
        {
            count++;
        }
    }
    cout << count;
    return 0;
}

题目D:(R格式)

思路:

一开始想到快速幂运算:

cpp 复制代码
// 快速幂计算 a^n(n >= 0)
long long fastPower(long long a, long long n) {
    long long result = 1;  // 结果初始化为1(任何数的0次方为1)
    long long base = a;    // 当前底数,初始为a
    
    while (n > 0) {
        // 如果指数二进制最后一位是1,将当前底数乘到结果中
        if (n % 2 == 1) {
            result *= base;
        }
        // 底数平方(对应指数二进制左移一位)
        base *= base;
        // 指数右移一位(等价于 n = n / 2)
        n /= 2;
    }
    return result;
}
cpp 复制代码
// 快速幂(位运算优化版)
long long fastPowerBit(long long a, long long n) {
    long long result = 1;
    long long base = a;
    
    while (n > 0) {
        // 位运算:判断最后一位是否为1(替代 n%2==1)
        if (n & 1) { 
            result *= base;
        }
        base *= base;
        // 位运算:右移一位(替代 n /= 2)
        n >>= 1; 
    }
    return result;
}

但是不行,因为快速幂适用于整数运算,如果是浮点数会放大误差。

必须用高精度,B站链接:【高精度算法全套(加,减,乘,除,全网最详细)】https://www.bilibili.com/video/BV1LA411v7mt/?share_source=copy_web&vd_source=c78a410f12ade1fd5af50917e0f2b35d

题解:(详细注释)

cpp 复制代码
using namespace std;
int n;          // 转换参数n,用于计算乘以2的n次方
int num[100005];// 存储浮点数的每一位数字(高精度数组)
string s;       // 输入的浮点数字符串
int l;          // 存储数字串的有效长度
int main()
{
    // 1. 输入转换参数n和浮点数字符串s
    cin >> n;
    cin >> s;
    // 反转字符串,方便从低位到高位处理(如123.45反转为54.321)
    reverse(s.begin(), s.end());
    // 找到反转后字符串中小数点的位置
    int p = s.find('.');
    // 移除小数点,将字符串转为纯数字串(如54.321转为54321)
    s.erase(p, 1);
    // 更新有效长度为移除小数点后的字符串长度
    l = s.size();
    // 将字符串的每一位字符转为数字,存入num数组(num[0]是最低位)
    for (int i = 0; i < l; i++)
    {
        num[i] = s[i] - '0';
    }

    // 2. 模拟乘以2的n次方:循环n次,每次整体乘以2并处理进位
    for (int i = 0; i < n; i++)
    {
        // 第一步:当前所有位乘以2
        for (int j = 0; j < l; j++)
        {
            num[j] *= 2;
        }
        // 第二步:处理每位的进位(满10进1)
        for (int j = 0; j < l; j++)
        {
            if (num[j] >= 10)
            {
                num[j + 1] += num[j] / 10; // 进位到高位
                num[j] %= 10;              // 保留当前位的个位
            }
        }
        // 如果最高位有进位,扩展有效长度
        if (num[l] > 0)
        {
            l++;
        }
    }
    // 3. 四舍五入处理:判断原小数点后对应位是否≥5,若是则进1
    if (num[p - 1] >= 5)
    {
        num[p]++; // 向高位进1
    }
    // 处理四舍五入后的进位(仅判断p+1位,原逻辑有局限)
    if (num[p + 1] >= 10)
    {
        for (int j = p; j < l; j++)
        {
            if (num[j] >= 10)
            {
                num[j + 1] += num[j] / 10;
                num[j] %= 10;
            }
            else
            {
                break; // 无进位则停止处理
            }
        }
    }
    // 若最高位因进位产生新数字,扩展有效长度
    if (num[l] > 0)
    {
        l++;
    }
    // 4. 输出结果:从最高位到原小数点位置的所有位(整数部分)
    for (int i = l - 1; i >= p; i--)
    {
        cout << num[i];
    }

    return 0;
}

需要注意的是:

四舍五入是需要再遍历一下左边,因为小数的四舍五入有可能会影响到最左(高)位,如 99.5 -> 100

题目E:(宝石组合)

思路:

S可以化简为 S = gcd ( Ha , Hb , Hc );

选三个H让他们的最大公因数最大;如果现在直接暴力肯定超时。

第一种思路:将每个数的公因数都存储下来,然后挨个遍历找到最大的出现三次的公因数,并记录这三个数。

再优化一下,用一个数组来计数,统计某个数作为公因数出现的次数,

代码段:

cpp 复制代码
 int N;
    cin >> N;
    vector<int> ns;
    int n = 100000;
    int nss[n] = {0};
    for (int i = 0; i < N; i++)
    {
        int j;
        cin >> j;
        ns.push_back(j);
        for (int k = 1; k*k <= j; k++)
        {
            if (j % k == 0)
            {
                nss[k]++;
                if (k*k != j)
                {
                    nss[j/k]++;
                }
            }
        }
    }

再倒着找到最先出现3次的公因数 M,然后再遍历一下排序后的原数组寻找是这个 M 公倍数的数字,知道找够3个。

题解

cpp 复制代码
using namespace std;
int n;
int num[100005];
string s;
int l;
int main()
{
    int N;
    cin >> N;
    vector<int> ns;
    int n = 100000;
    int nss[n] = {0};
    for (int i = 0; i < N; i++)
    {
        int j;
        cin >> j;
        ns.push_back(j);
        for (int k = 1; k*k <= j; k++)
        {
            if (j % k == 0)
            {
                nss[k]++;
                if (k*k != j)
                {
                    nss[j/k]++;
                }
            }
        }
    }

    sort(ns.begin(), ns.end());
    int M;
    vector<int> ans;
    for (int i = n - 1; i >= 0; i--)
    {
        if (nss[i] >= 3)
        {
            
            M = i;
            break;
        }
    }
    for (int i = 0; i < N; i++)
    {
        if (ns[i] % M == 0)
        {
            ans.push_back(ns[i]);
            if (ans.size() == 3)
            {
                break;
            }
        }
    }
    cout << ans[0] << " " << ans[1] << " " << ans[2] << endl;

    return 0;
}

题目F:(数字接龙)

思路:

这道题比较难。

如果不会用方向数组可能做不出来。首先根据提议写出方向数组,

代码段:

cpp 复制代码
int dy[] = {-1, -1, 0, 1, 1, 1, 0, -1};
int dx[] = {0, 1, 1, 1, 0, -1, -1, -1};

首先用一个二维数组存储地图,一个二维数组存储地图各个地方是否走到过。

然后DFS递归8个方向。并且需要

  1. 越界判断,
  2. 防止交叉搜索,
  3. 是否符合移动规则,
  4. 回溯

题解:

cpp 复制代码
using namespace std;

string ans;
int dx[] = {-1, -1, 0, 1, 1, 1, 0, -1};
int dy[] = {0, 1, 1, 1, 0, -1, -1, -1};

int n, k;
int a[11][11];
bool vis[11][11];

bool dfs(int x, int y, int pre, string s, int d) // x,y为当前坐标,pre为上一个数字,s为方向字符串,d深度
{
    if (x == n && y == n && d == n * n)
    {
        ans = s;
        return true;
    }

    // 从0到7枚举方向,对应8个方向(保证字典序最小)
    for (int i = 0; i < 8; i++)
    {
        // 随机移动后的坐标
        int nx = x + dx[i];
        int ny = y + dy[i];

        // 越界判断
        if (nx < 1 || nx > n || ny < 1 || ny > n)
        {
            continue;
        }
        if (vis[nx][ny])
        {
            continue;
        }

        // 防止交叉搜索
        if (i == 1 && vis[x - 1][y] && a[x][y + 1])
        {
            continue;
        }
        else if (i == 3 && vis[x + 1][y] && vis[x][y + 1])
        {
            continue;
        }
        else if (i == 5 && vis[x][y - 1] && a[x + 1][y])
        {
            continue;
        }
        else if (i == 7 && vis[x - 1][y] && a[x][y - 1])
        {
            continue;
        }

        // 判断是否符合移动规则
        if ((a[nx][ny] == pre + 1 && a[nx][ny] < k) || (a[nx][ny] == 0 && pre == k - 1))
        {
            // 标记为已访问
            vis[nx][ny] = true;
            dfs(nx, ny, a[nx][ny], s + to_string(i), d + 1);

            // 剪枝
            if (!ans.empty())
            {
                return true;
            }

            // 回溯
            vis[nx][ny] = false;
        }
    }
}

int main()
{
    cin >> n >> k;

    vis[1][1] = true;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> a[i][j];
        }
    }

    string s;
    dfs(1, 1, 0, s, 1);
    
    if (ans.empty())
    {
        cout << -1;
    }

    else
    {
        cout << ans;
    }
}

题目G:(爬山)

思路:

肯定要有限使用 P 魔法啊。

刚开始就想到用 multiset 其实用堆更好一点

题解:(multiset)

cpp 复制代码
using namespace std;

int n, P, Q;
multiset<int> H;
int ans;

int main()
{
    cin >> n >> P >> Q;
    for (int i = 0; i < n; i++)
    {
        int j;
        cin >> j;
        H.insert(j);
    }
    for(int i = 0;i<P;i++){
        int j = *H.rbegin();
        H.erase(H.find(j));
        H.insert(sqrt(j));
    }
    for(int i = 0;i<Q;i++){
        int j = *H.rbegin();
        H.erase(H.find(j));
        H.insert(j/2);
    }
    for (auto num : H)
    {
        ans += num;
    }
    cout << ans;
    return 0;
}

题解:(堆)

cpp 复制代码
using namespace std;

int n, P, Q;
int ans;
priority_queue<int> H;

// 使用堆实现
int main()
{
    cin >> n >> P >> Q;
    for (int i = 0; i < n; i++)
    {
        int j;
        cin >> j;
        H.push(j);
    }
    for(int i = 0;i<P;i++){
        int j = H.top();
        H.pop();
        H.push(sqrt(j));
    }
    for(int i = 0;i<Q;i++){
        int j = H.top();
        H.pop();
        H.push(j/2);
    }
    while (!H.empty())
    {
        H.pop();
        ans += H.top();
    }
    cout << ans;
    return 0;
}

题目H:(拔河)

思路:

两个小队的成员必须是连续不间断的,

但是我一开始只考虑了部分的情况:[ 1,2,3]和[ 5,6] 这种没有考虑 [ 2,3] 和 [ 5] 这种,

也就是每一队总是包含头和尾是不全面的。

用 lsum [ i ] 存储从首到 i 位置的力量和,rsum [ i ] 存储从 i 到尾的力量和。

然后两个 for 循环遍历求解。

代码:
cpp 复制代码
int main()
{
    int n;
    cin >> n;
    vector<int> nums;
    for (int i = 0; i < n; i++)
    {
        int j;
        cin >> j;
        nums.push_back(j);
    }

    vector<int> lsum;
    vector<int> rsum;
    int pre = 0;
    for (int i = 0; i < n; i++)
    {
        pre += nums[i];
        lsum.push_back(pre);
    }
    pre = 0;
    for (int i = n - 1; i >= 0; i--)
    {
        pre += nums[i];
        rsum.push_back(pre);
    }

    int m = INT_MAX;
    for (int i = 0; i < n; i++)
    {
        for (int j = i + 1; j < n; j++)
        {
            m = min(m, abs(lsum[i] - rsum[n - j - 1]));
        }
    }

    cout << m;
    return 0;
}

所以我们需要考虑更多的情况,但是还按照这个思路看定超时。

需要新的思路:

我们肯定需要前缀和,(后缀和 = 总和 - 前缀和)。并有前缀和计算出所有的组合的和。

所有的组合肯定有很多种,我们用 multiset 而不是 set 。

|--------------|-----------|-------------|
| 特性 | set | multiset |
| 重复元素 | 不允许(自动去重) | 允许(可存多个相同值) |
| 插入逻辑 | 行通知只能存一个 | 相同值全部存储 |
| erase(值)行为 | 删除所有该值的元素删除所有该值的元素 ||
| erase(迭代器)行为 | 删除迭代器指向的元素删除迭代器指向的元素 ||
| 查找 find(值) | 返回第一个匹配的迭代器(无则 end()) ||

然后以 i 为左队和右队的分界点(从 1 开始遍历,i 属于左队),

并进行:

cpp 复制代码
// 从S中删除「所有以i为起点的子数组和」
        for (int j = i; j <= n; j++)
        {
            // 以迭代器的方式删除(避免删除全部相同元素)
            auto p = S.find(a[j] - a[i - 1]);
            S.erase(p); // 删除第一个匹配元素
        }

因为这些数肯定不属于右队。而左队我们可以用前缀和遍历得到 k ,然后再在剩下的multiset中**(其实也就是右队所有可能的组合和,(因为我们依次进行这样的操作能够把multiset中的左队删掉,只剩下右队的组合和))**!!这是关键。

然后:

cpp 复制代码
        // 枚举所有以i为终点的子数组和
        for (int j = 1; j <= i; j++)
        {
            long long k = a[i] - a[j - 1];
            // 找到大于等于k的第一个元素的迭代器
            auto p = S.lower_bound(k);
            if (p != S.end()) // 如果找到
                ans = min(ans, abs(*p - k));
            if (p != S.begin()) // 如果找到且不是第一个元素
                // 比较前一个元素与k的差值(可能更小)
                p--, ans = min(ans, abs(*p - k));
        }

题解:(正解)

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

const int maxn = 1e3 + 10;
int n;
long long a[maxn], ans = 1e9;
multiset<long long> S; // 没有使用set

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i], a[i] = a[i - 1] + a[i]; // 前缀和维护
    }

    for (int i = 1; i <= n; i++)
    {
        for (int j = i; j <= n; j++)
        {
            S.insert(a[j] - a[i - 1]); // 全部插入
        }
    }

    for (int i = 1; i < n; i++)
    {
        // 从S中删除「所有以i为起点的子数组和」
        for (int j = i; j <= n; j++)
        {
            // 以迭代器的方式删除(避免删除全部相同元素)
            auto p = S.find(a[j] - a[i - 1]);
            S.erase(p); // 删除第一个匹配元素
        }

        // 枚举所有以i为终点的子数组和
        for (int j = 1; j <= i; j++)
        {
            long long k = a[i] - a[j - 1];
            // 找到大于等于k的第一个元素的迭代器
            auto p = S.lower_bound(k);
            if (p != S.end()) // 如果找到
                ans = min(ans, abs(*p - k));
            if (p != S.begin()) // 如果找到且不是第一个元素
                // 比较前一个元素与k的差值(可能更小)
                p--, ans = min(ans, abs(*p - k));
        }
    }
    cout << ans;
    return 0;
}
相关推荐
学嵌入式的小杨同学2 小时前
STM32 进阶封神之路(二十二):DMA 实战全攻略 ——ADC 采集 + 串口收发 + 内存复制(库函数 + 代码落地)
c++·stm32·单片机·嵌入式硬件·mcu·硬件架构·pcb
2401_884563242 小时前
C++中的观察者模式实战
开发语言·c++·算法
Engineer邓祥浩2 小时前
JVM学习问题记录(1) IDEA2025设置JVM启动参数
jvm·学习
qcwl662 小时前
深入理解Linux进程与内存 学习笔记#3
linux·笔记·学习
-Springer-2 小时前
STM32 学习 —— 个人学习笔记10-1(I2C 通信协议及 MPU6050 简介 & 软件 I2C 读写 MPU6050)
笔记·stm32·学习
凌波粒2 小时前
LeetCode--704.二分查找(数组)
算法·leetcode·职场和发展
xiaoye-duck2 小时前
《算法题讲解指南:动态规划算法--路径问题》--11.按摩师,12.打家劫舍II
c++·算法·动态规划
历程里程碑2 小时前
43. TCP -2实现英文查中文功能
java·linux·开发语言·c++·udp·c#·排序算法
小陈phd2 小时前
多模态大模型学习笔记(二十二)——大模型微调全解:从全量调参到LoRA的参数高效训练实战
笔记·学习