简介

这是一份关于2024年CCF非专业级软件能力认证(CSP-J/S 2024)第二轮认证入门级考试的详细信息说明。以下是对这份说明的详细解读:
考试基本信息
时间:2024年10月26日 08:30 - 12:00
题目名称:
扑克牌(poker)
地图探险(explore)
小木棍(sticks)
接龙(chain)
题目类型:均为传统型题目
文件命名与输入输出
可执行文件名:对应每个题目,分别为 poker, explore, sticks, chain
输入文件名:对应每个题目,分别为 poker.in, explore.in, sticks.in, chain.in
输出文件名:对应每个题目,分别为 poker.out, explore.out, sticks.out, chain.out
限制条件
每个测试点时限:
扑克牌、地图探险、小木棍:1.0秒
接龙:2.0秒
内存限制:每个题目均为512 MiB
测试点数目:
扑克牌、地图探险、小木棍:10个
接龙:20个
测试点是否等分:是(意味着每个测试点的分数相同)
注意事项
文件名:必须使用英文小写。
main函数返回值:必须是int类型,程序正常结束时返回值必须是0。
程序代码文件放置位置:需参考各省的具体要求。
错误处理:因违反文件名、返回值类型或程序放置位置要求而出现的错误,申诉时不予受理。
结果比较方式:全文比较,过滤行末空格及文末回车。
程序源文件大小:必须不大于100KB。
栈空间内存限制:与题目的内存限制一致。
评测机器配置:Intel(R) Core(TM) i7-8700K CPU @3.70GHz,内存32GB;时限以此配置为准。
样例文件:只提供Linux格式附加样例文件。
评测环境:在当前最新公布的NOI Linux下进行,各语言的编译器版本以此为准。
提交要求
对于C++语言,提交的程序文件名应为 poker.cpp, explore.cpp, sticks.cpp, chain.cpp。
编译选项为 -O2 -std=c++14 -static。
总结
这份说明详细列出了考试的时间、题目、输入输出文件名、限制条件以及注意事项等关键信息。考生在准备和提交程序时,必须严格遵守这些规定,以避免因不符合要求而导致的错误或问题。同时,考生还需要注意程序的效率和正确性,以确保在规定的时限和内存限制内完成题目的解答。
解析
1.扑克牌(poker)



要解决这个问题,我们需要统计输入的扑克牌中每种花色和点数的牌的数量,然后计算还需要借多少张牌才能构成一副完整的扑克牌。
以下是详细的步骤:
- 读取输入:从文件中读取输入的牌的信息。
- 统计现有牌:使用二维数组或哈希表统计每种花色和点数的牌的数量。
- 计算缺少的牌:遍历统计结果,计算还需要多少张牌才能凑齐一副完整的扑克牌。
- 输出结果:将结果输出到文件中。
下面是完整的 C++ 代码实现:
#include <iostream>
#include <fstream>
#include <vector>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
ifstream infile("poker.in");
ofstream outfile("poker.out");
int n;
infile >> n;
// 使用哈希表记录扑克牌的数量,花色作为key,点数作为value中的一个维度
unordered_map<char, vector<int>> cardCount;
// 初始化哈希表
cardCount['D'] = vector<int>(13, 0); // 方片
cardCount['C'] = vector<int>(13, 0); // 草花
cardCount['H'] = vector<int>(13, 0); // 红桃
cardCount['S'] = vector<int>(13, 0); // 黑桃
// 点数映射,将字符映射到索引
unordered_map<char, int> pointIndex = {
{'A', 0}, {'2', 1}, {'3', 2}, {'4', 3}, {'5', 4}, {'6', 5}, {'7', 6},
{'8', 7}, {'9', 8}, {'T', 9}, {'J', 10}, {'Q', 11}, {'K', 12}
};
// 读取输入的牌
for (int i = 0; i < n; ++i) {
string card;
infile >> card;
char suit = card;
char point = card;
cardCount[suit][pointIndex[point]]++;
}
// 计算还需要多少张牌
int totalNeeded = 0;
for (const auto& suitCount : cardCount) {
for (int count : suitCount.second) {
if (count == 0) {
totalNeeded++;
}
}
}
// 输出结果
outfile << totalNeeded << endl;
infile.close();
outfile.close();
return 0;
}
解释
-
文件操作:
ifstream infile("poker.in");用于从文件poker.in中读取输入。ofstream outfile("poker.out");用于将结果输出到文件poker.out中。
-
统计牌的数量:
- 使用
unordered_map<char, vector<int>> cardCount;来统计每种花色和点数的牌的数量。 vector<int>(13, 0)初始化每个花色的点数统计,因为点数有 13 种。
- 使用
-
点数映射:
- 使用
unordered_map<char, int> pointIndex将点数字符映射到相应的索引,方便后续统计。
- 使用
-
读取输入牌:
- 循环读取每张牌,更新
cardCount中相应的花色和点数的计数。
- 循环读取每张牌,更新
-
计算还需要的牌:
- 遍历
cardCount,统计每种花色中缺少的牌的数量。
- 遍历
-
输出结果:
- 将结果输出到文件
poker.out中。
- 将结果输出到文件
这段代码能够有效地统计出还需要借多少张牌才能构成一副完整的扑克牌,并满足题目中的所有要求。
2.地图探险(explore)




为了解决这个问题,我们需要模拟机器人在给定地图上的移动过程,并记录它经过的所有位置。以下是详细的解决步骤和C++代码实现:
解决步骤
-
读取输入:从文件中读取测试数据的组数、每组数据的地图大小、机器人初始位置和朝向,以及地图信息。
-
模拟机器人移动:
- 根据机器人的当前位置和朝向,计算下一步的位置。
- 检查下一步的位置是否在地图内且为空地。
- 如果下一步可行,更新机器人的位置,保持朝向不变。
- 如果下一步不可行,更新机器人的朝向(向右转),位置保持不变。
-
记录经过的位置:使用集合(
set)来记录机器人经过的所有位置,以避免重复计数。 -
输出结果:对每组数据,输出机器人经过的位置数量。
C++代码实现
#include <iostream>
#include <fstream>
#include <vector>
#include <set>
#include <string>
using namespace std;
// 定义方向数组,用于根据朝向计算下一步的位置
const int dx[] = {0, 1, 0, -1}; // 东、南、西、北对应的x方向变化
const int dy[] = {1, 0, -1, 0}; // 东、南、西、北对应的y方向变化
int main() {
ifstream infile("explore.in");
ofstream outfile("explore.out");
int T;
infile >> T;
while (T--) {
int n, m, k;
infile >> n >> m >> k;
int x0, y0, d0;
infile >> x0 >> y0 >> d0;
// 读取地图信息
vector<string> map(n);
for (int i = 0; i < n; ++i) {
infile >> map[i];
}
// 记录机器人经过的位置
set<pair<int, int>> visited;
int x = x0, y = y0, d = d0;
visited.insert({x, y});
// 模拟机器人移动
for (int step = 0; step < k; ++step) {
int nx = x + dx[d];
int ny = y + dy[d];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m && map[nx - 1][ny - 1] == '.') {
// 下一步在地图内且为空地,更新位置
x = nx;
y = ny;
} else {
// 下一步不可行,向右转
d = (d + 1) % 4;
}
// 记录经过的位置
visited.insert({x, y});
}
// 输出结果
outfile << visited.size() << endl;
}
infile.close();
outfile.close();
return 0;
}
代码解释
-
输入读取:
- 使用
ifstream从文件explore.in中读取输入。 - 读取测试数据组数
T。 - 对每组数据,读取地图大小
n和m、机器人操作次数k、机器人初始位置x0和y0及朝向d0。 - 读取地图信息,存储在
vector<string>中。
- 使用
-
模拟移动:
- 使用
set记录机器人经过的位置,以避免重复。 - 使用方向数组
dx和dy根据朝向计算下一步的位置。 - 检查下一步是否在地图内且为空地,更新机器人的位置和朝向。
- 将每一步的位置加入
set中。
- 使用
-
输出结果:
- 对每组数据,输出
set的大小,即机器人经过的位置数量。 - 使用
ofstream将结果写入文件explore.out中。
- 对每组数据,输出
3.小木棍(sticks)


为了解决这个问题,我们可以使用广度优先搜索(BFS)来找到使用给定数量小木棍能够拼出的最小正整数。BFS 是一种逐层遍历树或图的算法,适用于在最短路径、最小步数等场景下寻找最优解。
在这个问题中,我们可以将每个拼出的数字视为图中的一个节点,每次添加一个数字到当前数字的末尾可以视为节点之间的边。初始节点是空字符串(表示拼出的数为 0,但这不是一个有效的解,因为我们需要正整数),然后我们通过 BFS 逐层添加数字,直到找到满足条件的最小正整数或者确定无法找到。
以下是 C++ 代码的实现,该代码从文件 sticks.in 中读取输入,并将结果输出到文件 sticks.out 中:
#include <iostream>
#include <fstream>
#include <vector>
#include <queue>
#include <unordered_set>
using namespace std;
// 每个数字拼出时所需的小木棍数量
const vector<int> stickCounts = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
// 使用 BFS 寻找使用恰好 n 根小木棍能拼出的最小正整数
int findMinNumber(int n) {
// 队列元素是 pair<当前拼出的数字字符串, 已使用的木棍数>
queue<pair<string, int>> q;
// 用于记录已经访问过的木棍数,避免重复计算
unordered_set<int> visited;
// 初始化,空字符串表示开始,木棍使用数为 0
q.push({"", 0});
visited.insert(0);
while (!q.empty()) {
auto [numStr, sticksUsed] = q.front();
q.pop();
// 如果已经用了 n 根木棍且拼出的是正整数(非空且不以 0 开头)
if (sticksUsed == n && !numStr.empty() && numStr != '0') {
return stoi(numStr); // 将字符串转换为整数并返回
}
// 尝试在当前数字后面添加 0-9 的每个数字
for (char digit = '0'; digit <= '9'; ++digit) {
int newSticksUsed = sticksUsed + stickCounts[digit - '0'];
// 如果新木棍数不超过 n 且没有被访问过
if (newSticksUsed <= n && visited.find(newSticksUsed) == visited.end()) {
string newNumStr = numStr + digit;
q.push({newNumStr, newSticksUsed});
visited.insert(newSticksUsed);
}
}
}
// 无法拼出满足条件的正整数
return -1;
}
int main() {
ifstream inputFile("sticks.in");
ofstream outputFile("sticks.out");
int T;
inputFile >> T;
vector<int> results;
for (int i = 0; i < T; ++i) {
int n;
inputFile >> n;
results.push_back(findMinNumber(n));
}
for (int result : results) {
outputFile << result << endl;
}
inputFile.close();
outputFile.close();
return 0;
}
代码说明:
-
输入与输出:
- 使用
ifstream和ofstream类从文件sticks.in中读取输入,并将结果输出到文件sticks.out中。 - 输入的第一行是测试数据的组数
T,接下来是T个整数,每个整数表示一组测试数据中的木棍数n。
- 使用
-
BFS 搜索:
- 使用一个队列
q来存储当前搜索状态,每个状态是一个pair<string, int>,分别表示当前拼出的数字字符串和已使用的木棍数。 - 使用一个
unordered_set来记录已经访问过的木棍数,以避免重复计算。 - 初始化时,将空字符串和木棍数 0 加入队列。
- 在 BFS 过程中,每次从队列中取出一个状态,尝试在当前数字后面添加 0-9 的每个数字,计算新的木棍数,并将合法的新状态加入队列。
- 如果找到满足条件的数字(即木棍数恰好为
n且数字为正整数),则将其转换为整数并返回。
- 使用一个队列
4.接龙(chain)




温馨提示:本人实力有限,这题的解析可能不准确,各位大佬不喜可喷~
这个问题是一个复杂的搜索和判断问题,涉及多个人、多个词库和多轮接龙任务。为了解决这个问题,我们需要考虑以下几点:
-
数据结构选择:
- 我们需要高效地存储和查找每个人的词库,因此可以使用
vector或map等数据结构。
- 我们需要高效地存储和查找每个人的词库,因此可以使用
-
搜索策略:
- 对于每个任务,我们需要搜索是否存在一种接龙序列满足要求。这可以通过深度优先搜索(DFS)或广度优先搜索(BFS)来实现。
-
剪枝优化:
- 在搜索过程中,通过剪枝来提前排除不符合条件的情况,提高搜索效率。
-
多组数据:
- 题目要求处理多组数据,因此每组数据都需要独立处理,并且要注意重置状态。
下面是一个可能的C++实现思路:
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <fstream>
#include <cstring>
using namespace std;
// 用于存储每个人的词库
struct Player {
int len;
vector<int> words;
};
// 判断某个序列是否是另一个序列的子序列
bool isSubsequence(const vector<int>& seq, const vector<int>& sub) {
int i = 0, j = 0;
while (i < seq.size() && j < sub.size()) {
if (seq[i] == sub[j]) {
j++;
}
i++;
}
return j == sub.size();
}
// DFS搜索
bool dfs(int round, int lastPerson, int lastWord, int targetRound, int targetWord, vector<Player>& players, int k, vector<int>& currentPath, vector<vector<bool>>& visited) {
if (round == targetRound) {
return currentPath.back() == targetWord;
}
for (int i = 0; i < players.size(); i++) {
if (i == lastPerson) continue; // 不能和上一轮相同
if (visited[i][round]) continue; // 避免重复搜索
for (int j = 0; j <= players[i].len - k; j++) {
// 截取长度为 [2, k] 的子序列
for (int l = 2; l <= k && j + l <= players[i].len; l++) {
vector<int> subSeq(players[i].words.begin() + j, players[i].words.begin() + j + l);
if (subSeq.front() == lastWord && isSubsequence(players[i].words, subSeq)) {
visited[i][round] = true;
currentPath.push_back(subSeq.back());
if (dfs(round + 1, i, subSeq.back(), targetRound, targetWord, players, k, currentPath, visited)) {
return true;
}
currentPath.pop_back();
visited[i][round] = false;
}
}
}
}
return false;
}
int main() {
ifstream infile("chain.in");
ofstream outfile("chain.out");
int T;
infile >> T;
while (T--) {
int n, k, q;
infile >> n >> k >> q;
vector<Player> players(n);
for (int i = 0; i < n; i++) {
infile >> players[i].len;
players[i].words.resize(players[i].len);
for (int j = 0; j < players[i].len; j++) {
infile >> players[i].words[j];
}
}
for (int i = 0; i < q; i++) {
int r, c;
infile >> r >> c;
vector<int> currentPath = {1}; // 第一轮必须以1开头
vector<vector<bool>> visited(n, vector<bool>(r + 1, false));
if (dfs(1, -1, 1, r, c, players, k, currentPath, visited)) {
outfile << "1\n";
} else {
outfile << "0\n";
}
}
}
return 0;
}
结束了!
对了,忘说了一句话:
要想c++成绩好,就来jiabei小课堂。
还有,点我主页,看我简介,别给那三个人点赞就完了
最后留一句话:第四题真TM难啊
4552





