蓝桥杯CC++竞赛B组8题解析与算法技巧
2026/9/20 6:53:10 网站建设 项目流程

1. 蓝桥杯C&C++大学B组竞赛解析

作为一名参加过多次算法竞赛的老手,我深知蓝桥杯这类比赛对编程能力提升的重要性。今天我将详细拆解第十五届蓝桥杯C&C++大学B组的8道典型题目,从基础题到压轴题,手把手带你理解解题思路和实现细节。

算法竞赛的核心在于快速理解题意、设计高效解法并准确实现代码。这需要扎实的数据结构基础、熟练的编码能力以及丰富的解题经验。下面我将按照题目难度递进的顺序,逐一分析每道题的解题思路和代码实现。

2. 题目A:握手问题

2.1 问题描述

有49个人参加聚会,其中7个人比较特殊。普通参与者两两之间都会握手一次,特殊参与者会与所有普通参与者握手。求总共发生的握手次数。

2.2 解题思路

这道题考察基础的组合数学知识。我们可以将问题分解为两部分:

  1. 普通参与者之间的握手:42人中任选2人,组合数为C(42,2)
  2. 特殊参与者与普通参与者的握手:每个特殊参与者与42个普通参与者握手,共7×42次

总握手次数 = C(42,2) + 7×42 = 861 + 294 = 1155

2.3 代码实现

#include <iostream> using namespace std; int main() { int sum = 0; // 计算普通参与者之间的握手次数 for (int n = 1; n <= 42; n++) { sum += n; // 等价于C(42,2) } // 加上特殊参与者与普通参与者的握手次数 sum += 7 * 43; // 注意题目描述可能有歧义,这里按42计算 cout << sum; return 0; }

2.4 注意事项

  • 题目描述中"7个人特殊就让他们排到最后握手"这句话可能有歧义,需要明确特殊参与者的握手规则
  • 组合数计算可以用公式C(n,2)=n(n-1)/2,避免循环累加
  • 实际编码时建议添加注释说明计算逻辑

3. 题目B:小球反弹

3.1 问题描述

一个长343720、宽233333的长方形区域内,小球以15:17的速率比(水平:垂直)运动。求小球首次回到原点时经过的总路径长度(保留2位小数)。

3.2 暴力解法

3.2.1 思路
  1. 速率比为15:17,则路程比也为15:17
  2. 回到原点意味着水平和垂直方向的路程都是长/宽的偶数倍
  3. 枚举可能的倍数i和j,找到满足15×宽×j = 17×长×i的最小解
3.2.2 代码
#include<bits/stdc++.h> using namespace std; int main() { // 暴力枚举 for(long long i=2; i<=10000; i+=2) for(long long j=2; j<=100000; j+=2) if(15*233333*j == 17*343720*i) { cout << fixed << setprecision(2) << sqrt((343720*i)*(343720*i)+(233333*j)*(233333*j)); return 0; } return 0; }

3.3 数学优化解法

3.3.1 思路

将问题转化为求最小公倍数:

  1. 设水平路程X = 2a×长,垂直路程Y = 2b×宽
  2. 根据路程比关系:Y×15 = X×17
  3. 求满足条件的最小X和Y,即求最小公倍数
3.3.2 代码
#include <iostream> #include <cmath> #include <algorithm> using namespace std; int main() { double sum = 0; long long y = 233333; long long x = 343720; long long dy = 17; long long dx = 15; // 计算最大公约数 long long g = __gcd(dx * y * 2, dy * x * 2); // 计算最小公倍数 long long t = y * 2 * dx / g * x * 2 * dy; int a = t / (y * 2 * dx); int 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; }

3.4 经验分享

  • 填空题优先考虑暴力法,节省思考时间
  • 数学方法虽然高效但容易出错,建议双重验证
  • 注意数据范围,使用long long避免溢出

4. 题目C:好数

4.1 问题描述

如果一个数的各位数字中,奇数位(从右向左数)上的数字是奇数,偶数位上的数字是偶数,则称这个数为"好数"。给定N,求1到N中好数的个数。

4.2 解题思路

直接枚举1到N的每个数,逐位检查是否符合好数定义:

  1. 从最低位(第一位)开始,奇数位检查是否为奇数
  2. 偶数位检查是否为偶数
  3. 全部位满足条件则计数+1

4.3 代码实现

#include <iostream> #include <vector> using namespace std; int main() { int N, count = 0; cin >> N; for (int i = 1; i <= N; i++) { int n = i; bool isGood = true; for (int pos = 1; n > 0; pos++, n /= 10) { int digit = n % 10; // 奇数位检查是否为奇数,偶数位检查是否为偶数 if ((pos % 2) != (digit % 2)) { isGood = false; break; } } if (isGood) count++; } cout << count; return 0; }

4.4 优化建议

  • 对于大N可以考虑数位DP算法优化
  • 预处理数字的奇偶性判断结果
  • 使用位运算加速奇偶判断:(digit & 1) == (pos & 1)

5. 题目D:R格式

5.1 问题描述

给定浮点数d和整数n,将d乘以2^n后四舍五入到整数。要求高精度处理,避免浮点数精度误差。

5.2 解题思路

  1. 将浮点数转换为字符串处理,避免精度损失
  2. 移除小数点,记录小数点位置
  3. 模拟乘以2的n次方:每次乘以2并处理进位
  4. 根据原小数点位置进行四舍五入
  5. 输出整数部分

5.3 代码实现

#include <iostream> #include <string> #include <algorithm> using namespace std; int num[100005]; // 高精度数组 string s; int l, n; int main() { cin >> n >> s; // 反转并移除小数点 reverse(s.begin(), s.end()); int p = s.find('.'); s.erase(p, 1); l = s.size(); // 初始化高精度数组 for (int i = 0; i < l; i++) num[i] = s[i] - '0'; // 乘以2的n次方 for (int i = 0; i < n; i++) { // 每位乘以2 for (int j = 0; j < l; j++) num[j] *= 2; // 处理进位 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++; } // 四舍五入 if (num[p-1] >= 5) num[p]++; // 处理进位 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++; // 输出整数部分 for (int i = l-1; i >= p; i--) cout << num[i]; return 0; }

5.5 注意事项

  • 高精度运算要注意数组大小,防止溢出
  • 四舍五入后可能需要处理多级进位
  • 反转字符串处理可以简化小数点位置计算

6. 题目E:宝石组合

6.1 问题描述

给定N个宝石,每个宝石有能量值H。选择3个宝石,使其最大公约数S最大。输出能量值之和最小的组合(按升序排列)。

6.2 解题思路

  1. 统计每个数所有因数的出现次数
  2. 找到出现至少3次的最大因数M
  3. 选择能被M整除的最小的3个数

6.3 代码实现

#include <iostream> #include <vector> #include <algorithm> #include <numeric> using namespace std; int main() { int N; cin >> N; vector<int> ns(N); const int MAX = 100000; int cnt[MAX+1] = {0}; // 统计每个数的因数 for (int i = 0; i < N; i++) { cin >> ns[i]; for (int k = 1; k*k <= ns[i]; k++) { if (ns[i] % k == 0) { cnt[k]++; if (k*k != ns[i]) cnt[ns[i]/k]++; } } } // 排序以便选择最小的三个数 sort(ns.begin(), ns.end()); // 找最大的出现至少3次的因数 int M = 0; for (int i = MAX; i >= 1; i--) { if (cnt[i] >= 3) { M = i; break; } } // 选择能被M整除的最小的三个数 vector<int> ans; for (int num : ns) { if (num % M == 0) { ans.push_back(num); if (ans.size() == 3) break; } } cout << ans[0] << " " << ans[1] << " " << ans[2]; return 0; }

6.4 优化方向

  • 使用筛法预处理每个数的因数
  • 对于大数据可以考虑质因数分解优化
  • 使用优先队列维护前三个最小数

7. 题目F:数字接龙

7.1 问题描述

在n×n矩阵中,从(1,1)出发,按数字递增规则(0→1→...→k-1→0→1...)移动,求字典序最小的路径。移动方向对应数字0-7(上、右上、右等)。

7.2 解题思路

使用DFS+剪枝:

  1. 定义8个移动方向(按字典序排列)
  2. 维护访问标记,防止重复访问
  3. 检查移动是否符合数字递增规则
  4. 找到完整路径后立即返回(保证字典序最小)

7.3 代码实现

#include <iostream> #include <vector> #include <string> using namespace std; string ans; int dx[] = {-1,-1,0,1,1,1,0,-1}; // 8个方向 int dy[] = {0,1,1,1,0,-1,-1,-1}; int n, k, a[11][11]; bool vis[11][11]; bool dfs(int x, int y, int pre, string path) { if (x == n && y == n && path.length() == n*n-1) { ans = path; return true; } for (int i = 0; i < 8; i++) { int nx = x + dx[i], ny = y + dy[i]; // 边界检查 if (nx < 1 || nx > n || ny < 1 || ny > n || vis[nx][ny]) continue; // 数字规则检查 if ((a[nx][ny] == (pre+1)%k)) { vis[nx][ny] = true; if (dfs(nx, ny, a[nx][ny], path + to_string(i))) return true; vis[nx][ny] = false; } } return false; } int main() { cin >> n >> k; for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) cin >> a[i][j]; vis[1][1] = true; dfs(1, 1, a[1][1], ""); cout << (ans.empty() ? "-1" : ans); return 0; }

7.4 注意事项

  • 方向数组必须按字典序排列
  • 及时剪枝可以提高效率
  • 注意回溯时要恢复访问状态
  • 对于大矩阵可能需要优化搜索策略

8. 题目G:爬山

8.1 问题描述

有n座山,每座山有高度H。有两种魔法:

  1. P魔法:将山高变为√H(向下取整)
  2. Q魔法:将山高变为⌊H/2⌋

使用P魔法P次和Q魔法Q次,求所有山高之和的最小值。

8.2 解题思路

贪心算法:

  1. 每次选择当前最高的山
  2. 优先使用P魔法(因为√H的减少幅度大于H/2)
  3. 使用最大堆维护当前山高

8.3 代码实现

#include <iostream> #include <queue> #include <cmath> using namespace std; int main() { int n, P, Q, ans = 0; cin >> n >> P >> Q; priority_queue<int> heap; for (int i = 0; i < n; i++) { int h; cin >> h; heap.push(h); } // 优先使用P魔法 while (P-- > 0 && !heap.empty()) { int h = heap.top(); heap.pop(); heap.push(sqrt(h)); } // 再使用Q魔法 while (Q-- > 0 && !heap.empty()) { int h = heap.top(); heap.pop(); heap.push(h / 2); } // 计算总和 while (!heap.empty()) { ans += heap.top(); heap.pop(); } cout << ans; return 0; }

8.4 优化建议

  • 使用更高效的数据结构如Fibonacci堆
  • 预处理魔法效果,减少重复计算
  • 对于大数据量可以考虑并行处理

9. 题目H:拔河

9.1 问题描述

将n个人分成两个连续的小组,使两组力量和的差值最小。求最小差值。

9.2 解题思路

  1. 计算前缀和数组
  2. 枚举所有可能的分割点i
  3. 对于每个i,计算前i人和后n-i人的和
  4. 维护最小差值

优化:使用multiset存储所有可能的子数组和,通过二分查找快速找到最接近的值。

9.3 代码实现

#include <iostream> #include <vector> #include <set> #include <climits> using namespace std; int main() { int n; cin >> n; vector<long long> prefix(n+1, 0); multiset<long long> sums; for (int i = 1; i <= n; i++) { cin >> prefix[i]; prefix[i] += prefix[i-1]; } // 预计算所有子数组和 for (int i = 1; i <= n; i++) for (int j = i; j <= n; j++) sums.insert(prefix[j] - prefix[i-1]); long long ans = LLONG_MAX; for (int i = 1; i < n; i++) { // 删除以i开头的子数组和 for (int j = i; j <= n; j++) { auto it = sums.find(prefix[j] - prefix[i-1]); sums.erase(it); } // 枚举以i结尾的子数组和 for (int j = 1; j <= i; j++) { long long left = prefix[i] - prefix[j-1]; auto it = sums.lower_bound(left); if (it != sums.end()) ans = min(ans, abs(*it - left)); if (it != sums.begin()) ans = min(ans, abs(*--it - left)); } } cout << ans; return 0; }

9.4 性能分析

  • 时间复杂度:O(n² log n)
  • 空间复杂度:O(n²)
  • 对于大数据量可能需要更优的算法

10. 竞赛经验总结

通过这8道题的解析,我们可以总结出以下算法竞赛的通用技巧:

  1. 理解题意优先:确保完全理解题目要求和边界条件
  2. 选择合适算法:根据数据规模选择暴力或优化算法
  3. 注意特殊条件:如连续子数组、字典序等限制
  4. 合理使用STL:熟练运用set、map、priority_queue等容器
  5. 重视边界处理:特别是数组越界和空输入情况
  6. 优化输入输出:对于大数据量使用快速IO方法
  7. 保持代码整洁:良好的代码结构便于调试和修改

在实际比赛中,建议:

  • 先解决简单题确保基础分
  • 对中等题快速实现可行解
  • 难题先写暴力解法再逐步优化
  • 合理分配时间,避免卡在一道题上

算法能力的提升需要持续练习和总结。建议定期参加在线判题平台的比赛,分析优秀选手的解题报告,不断积累经验和技巧。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询