第十六届蓝桥杯省赛(软件类真题)C/C++ 大学A组

第十六届蓝桥杯省赛(软件类真题)C/C++ 大学A组

大纲:

A.寻找质数
B:黑白棋

题目&解析&代码

A题

在这里插入图片描述

题目解析

本题的目标是枚举质数并计数,直到数到第2025个。由于2025不算太大,第2025个质数大约在17000~18000之间,完全可以在合理时间内通过简单枚举得到。

解题步骤:

从2开始遍历每个整数,判断它是否是质数。

质数判断采用试除法:对于一个数n,只需检查从2到√n的所有整数是否能整除n。若存在能整除的数,则n不是质数;否则是质数。

每找到一个质数,计数器加1。

当计数器达到2025时,输出当前的质数并结束。

优化点:

除了2以外,偶数不可能是质数,因此可以跳过偶数判断(直接步进2)。

在isPrime函数中,可以先处理特殊情况(n<2返回false),然后单独判断偶数,再对奇数进行试除,步进也可以设为2。

C++ 参考代码
以下代码实现了上述算法,并输出第2025个质数。

cpp
#include
#include
using namespace std;

// 判断一个整数是否为质数
bool isPrime(int n) {
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false; // 偶数直接排除
int limit = sqrt(n); // 只需检查到平方根
for (int i = 3; i <= limit; i += 2) { // 只检查奇数因子
if (n % i == 0) return false;
}
return true;
}

int main() {
int count = 0; // 已找到的质数个数
int num = 2; // 从2开始检查
while (true) {
if (isPrime(num)) {
count++;
if (count == 2025) {
cout << num << endl;
break;
}
}
num++;
}
return 0;
}
代码说明:

isPrime函数做了常规的优化:先排除2以外的偶数,然后只检查奇数因子到平方根。

main函数中循环递增num,每遇到一个质数计数器加1,直到第2025个时输出该数。

运行结果
在本地运行上述代码,最终输出的数字即为第2025个质数。根据已知质数表和实际计算,该数为:

17627

因此,本题的答案是 17627。

B题

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


🧩 题目回顾与核心规则
我们首先要明确游戏的胜利条件。在一个 6x6 的棋盘上,已有部分格子填有黑色 (1) 或白色 (0) 棋子,需要填满所有空格,并满足以下规则:

数量相等:每一行和每一列中,黑棋和白棋的数量必须相等(即各有3个)。

无三连:在任何一行或一列中,不能有超过两个相同颜色的棋子连续排列(即禁止出现“111”或“000”)。

行列唯一:每一行的排列方式必须是唯一的(不能与其他任何行相同);每一列的排列方式也必须是唯一的(不能与其他任何列相同)。但行与列之间可以相同。

题目给定的初始棋盘状态(基于选手回忆和题解推导)如下所示,其中 -1 代表空格:

cpp
int grid[6][6] = {
{ 1, 0, 1, 0,-1,-1},
{-1,-1,-1, 0,-1,-1},
{-1,-1,-1, 1, 0, 0},
{-1,-1,-1,-1,-1,-1},
{-1,-1, 1,-1,-1, 1},
{-1, 0,-1,-1, 1,-1}
};
💡 核心解题思路:深度优先搜索(DFS) + 剪枝
程序的核心思想是深度优先搜索(DFS),逐个格子地去尝试填入黑棋(1)或白棋(0)。但6x6的棋盘总共有36个格子,如果盲目地尝试所有可能,计算量是天文数字(2^36 ≈ 687亿种可能)。因此,必须在搜索过程中进行严格的剪枝,提前排除那些不可能满足规则的分支,才能快速找到唯一解。

主要的剪枝策略有:

数量限制:在放置一个棋子前,检查其所在行和列中,该颜色棋子数量是否已达到3个。如果已达到,就不能再放。

无三连限制:在放置一个棋子后,立即检查它是否与其左边两个(或上边两个)棋子构成了连续三个相同颜色。如果构成了,这个分支就是无效的,必须回退。

行列唯一性剪枝:每当填完一整行或一整列时,立即检查该行(或列)的排列是否与之前已完成的任何一行(或列)重复。如果重复,则剪枝。

💻 完整C++源码与逐行解析
下面这个程序正是基于上述思路实现的,代码中包含了详细的注释,可以帮助你理解每一步的作用。

cpp
#include<bits/stdc++.h>
using namespace std;

// 初始化棋盘,-1表示空格。这个状态是根据题目描述和选手回忆确定的。
int grid[6][6] = {
{ 1, 0, 1, 0,-1,-1},
{-1,-1,-1, 0,-1,-1},
{-1,-1,-1, 1, 0, 0},
{-1,-1,-1,-1,-1,-1},
{-1,-1, 1,-1,-1, 1},
{-1, 0,-1,-1, 1,-1}
};

// 用于记录当前每行和每列中黑棋(1)和白棋(0)的数量
int black_row[6] = {0}, black_col[6] = {0};
int white_row[6] = {0}, white_col[6] = {0};

// 用于记录已经出现过的行排列和列排列,以实现“行列唯一性”的剪枝
unordered_set vis_row, vis_col;
string ans; // 存储最终的答案字符串

// 函数:在搜索结束时,对整个棋盘进行一次最终检查,确保所有规则都被满足
int check() {
unordered_set r, c;
// 检查所有行是否唯一
for (int i = 0; i < 6; i++) {
string rr = “”;
for (int j = 0; j < 6; j++) rr += to_string(grid[i][j]);
if (r.count(rr)) return 0; // 发现重复行,无效
r.insert(rr);
}
// 检查所有列是否唯一
for (int i = 0; i < 6; i++) {
string cc = “”;
for (int j = 0; j < 6; j++) cc += to_string(grid[j][i]);
if (c.count(cc)) return 0; // 发现重复列,无效
c.insert(cc);
}
return 1; // 所有检查通过
}

// 核心:深度优先搜索函数,pos 表示当前正在处理的格子编号(从0到35)
int solve(int pos) {
// 如果所有格子都处理完了,进行最终检查
if (pos == 36) {
if (check()) {
// 将最终的棋盘状态转换为答案字符串
ans = “”;
for (int i = 0; i < 6; i++)
for (int j = 0; j < 6; j++) ans += to_string(grid[i][j]);
return 1; // 找到答案,返回1
}
return 0;
}

int row = pos / 6; // 计算当前格子所在行 int col = pos % 6; // 计算当前格子所在列 // 如果当前格子已经有棋子(非空格),则直接跳过,处理下一个格子 if (grid[row][col] != -1) return solve(pos + 1); // 尝试在当前空格放入两种颜色的棋子:val = 0 (白棋) 或 1 (黑棋) for (int val = 0; val <= 1; val++) { // 剪枝1:检查行和列中该颜色的数量是否已达上限(3个) if (!val) { // 尝试放白棋 if (white_row[row] >= 3 || white_col[col] >= 3) continue; } else { // 尝试放黑棋 if (black_row[row] >= 3 || black_col[col] >= 3) continue; } // 剪枝2:检查是否会形成横向或纵向的三个连续相同棋子 // 横向检查:当前格子的左边两个是否和当前尝试的val相同 if (col >= 2 && grid[row][col - 2] == val && grid[row][col - 1] == val) continue; // 纵向检查:当前格子的上边两个是否和当前尝试的val相同 if (row >= 2 && grid[row - 2][col] == val && grid[row - 1][col] == val) continue; // --- 执行放置操作,并更新相关计数 --- grid[row][col] = val; if (val) { black_row[row]++; black_col[col]++; } else { white_row[row]++; white_col[col]++; } // --- 剪枝3:如果当前放完了一整行或一整列,检查其唯一性 --- int flag = 1; // 标记是否通过唯一性检查 int full_row = (col == 5); // 是否刚填完一行(当col为5时,说明当前格子是行尾) int full_col = (row == 5); // 是否刚填完一列(当row为5时,说明当前格子是列尾) string,; if (full_row) { // 生成当前行的字符串表示 for (int i = 0; i < 6; i++) rowstr += to_string(grid[row][i]); if (vis_row.count(rowstr)) flag = 0; // 如果该行已出现过,则剪枝 else vis_row.insert(rowstr); // 否则,将其加入已出现行的集合 } if (full_col) { // 生成当前列的字符串表示 for (int i = 0; i < 6; i++) colstr += to_string(grid[i][col]); if (vis_col.count(colstr)) flag = 0; // 如果该列已出现过,则剪枝 else vis_col.insert(colstr); // 否则,将其加入已出现列的集合 } // 如果通过了所有剪枝,则递归地搜索下一个格子 if (flag && solve(pos + 1)) return 1; // --- 回溯:撤销当前尝试的操作,恢复现场,以便尝试另一种颜色的棋子 --- if (full_row) vis_row.erase(rowstr); if (full_col) vis_col.erase(colstr); grid[row][col] = -1; if (val) { black_row[row]--; black_col[col]--; } else { white_row[row]--; white_col[col]--; } } return 0; // 如果当前格子尝试了两种颜色都无法得到解,则返回0 

}

int main() {
// 程序开始前,先根据初始棋盘,初始化每行每列的黑白棋数量
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 6; j++) {
if (grid[i][j] == 1) {
black_row[i]++;
black_col[j]++;
} else if (grid[i][j] == 0) {
white_row[i]++;
white_col[j]++;
}
}
}
// 从第一个格子(编号0)开始深度优先搜索
if (solve(0)) {
cout << ans << endl; // 输出最终找到的答案字符串
}
return 0;
}

谢谢老铁们支持,下一期继续分享

Read more

吃透 C++ 栈和队列:stack/queue/priority_queue 用法 + 模拟 + STL 标准实现对比

吃透 C++ 栈和队列:stack/queue/priority_queue 用法 + 模拟 + STL 标准实现对比

✨ 孤廖:个人主页 🎯 个人专栏:《C++:从代码到机器》 🎯 个人专栏:《Linux系统探幽:从入门到内核》 🎯 个人专栏:《算法磨剑:用C++思考的艺术》 折而不挠,中不为下 文章目录 * 正文: * 容器适配器 * STL标准库中stack和queue的底层结构 * deque的简单介绍(了解) * deque的缺陷 * 为什么选择deque作为stack和queue的底层默认容器 * stack的介绍和使用 * Satck的介绍 * Stack的使用 * stack的模拟实现 * queue的介绍和使用 * queue的介绍 * queue的使用 * queue的模拟实现 * priority_queue的介绍和使用 * priority_queue的介绍 * priority_queue的使用 * 在OJ中的使用 * priority_queue的模拟实现 * STL标准库中对于sta

By Ne0inhk
Re:从零开始的 C++ STL篇(七)二叉搜索树增删查操作系统讲解(含代码)+key/key-value场景联合分析

Re:从零开始的 C++ STL篇(七)二叉搜索树增删查操作系统讲解(含代码)+key/key-value场景联合分析

◆ 博主名称: 晓此方-ZEEKLOG博客大家好,欢迎来到晓此方的博客。⭐️C++系列个人专栏: 主题曲:C++程序设计⭐️ 踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰 0.1概要&序論 这里是「此方」,好久不见。 今天我们要学习的是二叉搜索树。它是在普通二叉树的基础上加入特定约束,从而具备了高效的搜索能力。虽然这种结构能够支持高效的插入、删除与查找操作,但其性能背后也隐藏着潜在的 效率风险 。同时,在 key 与 key-value 两种不同的应用场景 下,二叉搜索树的设计与实现方式也会产生不同的变化。这里是「此方」。让我们现在开始吧! 前情提要,没有系统学习过一般二叉树的小伙伴直接看这篇文章可能会有些吃力,此方在这里留一个传送门:Re:从零开始的链式二叉树:建树、遍历、计数、查找、判全、销毁全链路实现与底层剖析 一,二叉搜索树的概念

By Ne0inhk
C++ 模板进阶:特化、萃取与可变参数模板

C++ 模板进阶:特化、萃取与可变参数模板

C++ 模板进阶:特化、萃取与可变参数模板 💡 学习目标:掌握模板进阶技术的核心用法,理解模板特化的深层应用、类型萃取的实现原理,以及可变参数模板的灵活使用,提升泛型编程的实战能力。 💡 学习重点:模板特化的进阶场景、类型萃取工具的设计与应用、可变参数模板的展开技巧、折叠表达式的使用方法。 一、模板特化进阶:处理复杂类型场景 💡 模板特化不只是针对单一类型的定制,还能处理指针、引用、数组等复杂类型,实现更精细的类型适配逻辑。 1.1 指针类型的模板特化 通用模板默认处理普通类型,我们可以为指针类型单独编写特化版本,实现指针专属的逻辑。 #include<iostream>#include<string>usingnamespace std;// 通用模板:处理普通类型template<typenameT>classTypeProcessor{public:staticvoidprocess(T data){ cout

By Ne0inhk

JSP 文件上传详解

JSP 文件上传详解 引言 在Web开发中,文件上传是一个常见的功能,它允许用户将文件从客户端发送到服务器。Java Server Pages(JSP)作为一种强大的服务器端技术,也支持文件上传功能。本文将详细讲解JSP文件上传的实现过程,包括技术原理、实现步骤和注意事项。 技术原理 JSP文件上传主要依赖于HTTP协议的multipart/form-data编码类型。这种编码类型允许表单中包含文件类型的输入字段。当用户提交表单时,浏览器会将表单数据以文件的形式发送到服务器。 服务器端使用Java的javax.servlet包中的HttpServletRequest和HttpServletResponse对象来接收这些文件。同时,javax.servlet包中的javax.servlet.http模块提供了Part接口,用于访问上传的文件内容。 实现步骤 以下是使用JSP实现文件上传的基本步骤: 1. 创建HTML表单 首先,我们需要创建一个HTML表单,其中包含一个文件类型的输入字段。以下是一个简单的示例: <form action="upload.jsp"

By Ne0inhk