3 条题解
-
0
这是一份功能完整的数据生成器,专门用于生成 CSP-J 2024 T1 扑克牌 的测试数据。
该生成器会生成 10 组数据(
1.in/out~10.in/out),严格覆盖了题目要求的 特殊性质 A(无重复)、特殊性质 B(已排序) 以及 包含重复牌 的各种边界情况。数据生成器代码 (gen.cpp)
将以下代码保存为
gen.cpp,编译运行即可。#include <iostream> #include <fstream> #include <string> #include <vector> #include <set> #include <algorithm> #include <random> #include <chrono> #include <map> using namespace std; // ========================================== // 第一部分:标准解答逻辑 (Solver) // 用于计算 .out 文件的正确答案 // ========================================== int solve(int n, const vector<string>& input_cards) { set<string> s; for (const string& card : input_cards) { s.insert(card); } return 52 - s.size(); } // ========================================== // 第二部分:扑克牌定义与工具 // ========================================== const string SUITS = "DCHS"; // 方片, 草花, 红桃, 黑桃 const string RANKS = "A23456789TJQK"; // 点数从小到大 // 获取完整的 52 张牌 vector<string> get_full_deck() { vector<string> deck; for (char r : RANKS) { for (char s : SUITS) { string card = ""; card += s; card += r; deck.push_back(card); // 此时生成的顺序并不是题目性质B要求的排序顺序,后续需处理 } } return deck; } // 题目特殊性质 B 的排序规则比较函数 // 点数从小到大,点数相同时按 D C H S int get_rank_index(char r) { return RANKS.find(r); } int get_suit_index(char s) { return SUITS.find(s); } bool compare_prop_B(const string& a, const string& b) { // a[1] 是点数, a[0] 是花色 int r1 = get_rank_index(a[1]); int r2 = get_rank_index(b[1]); if (r1 != r2) return r1 < r2; return get_suit_index(a[0]) < get_suit_index(b[0]); } // ========================================== // 第三部分:主程序 (生成 10 组数据) // ========================================== int main() { // 初始化随机数引擎 unsigned seed = chrono::system_clock::now().time_since_epoch().count(); mt19937 g(seed); cout << "正在生成 CSP-J 2024 扑克牌 测试数据..." << endl; for (int id = 1; id <= 10; id++) { string in_file = to_string(id) + ".in"; string out_file = to_string(id) + ".out"; ofstream fin(in_file); ofstream fout(out_file); int n; vector<string> current_cards; vector<string> deck = get_full_deck(); // --- 针对不同测试点生成数据 --- if (id == 1) { // 测试点 1: n=1, 特殊性质 A (互不相同) n = 1; shuffle(deck.begin(), deck.end(), g); current_cards.push_back(deck[0]); } else if (id >= 2 && id <= 4) { // 测试点 2-4: n <= 52, 特殊性质 A (互不相同) shuffle(deck.begin(), deck.end(), g); if (id == 2) n = 13; // 少量 else if (id == 3) n = 30; // 中量 else n = 52; // 满量 (答案应为 0) for(int i=0; i<n; ++i) { current_cards.push_back(deck[i]); } } else if (id >= 5 && id <= 7) { // 测试点 5-7: n <= 52, 特殊性质 B (点数从小到大,花色 DCHS) shuffle(deck.begin(), deck.end(), g); if (id == 5) n = 20; else if (id == 6) n = 45; else n = 52; // 完整一副牌,且有序 for(int i=0; i<n; ++i) { current_cards.push_back(deck[i]); } // 关键:进行符合性质 B 的排序 sort(current_cards.begin(), current_cards.end(), compare_prop_B); } else if (id == 8) { // 测试点 8: 无特殊性质,包含少量重复 n = 20; shuffle(deck.begin(), deck.end(), g); // 选 15 张不同的牌 for(int i=0; i<15; ++i) current_cards.push_back(deck[i]); // 再随机加 5 张重复的(从这 15 张里选) for(int i=0; i<5; ++i) current_cards.push_back(current_cards[i]); shuffle(current_cards.begin(), current_cards.end(), g); // 打乱顺序 } else if (id == 9) { // 测试点 9: 无特殊性质,包含大量重复 (极限情况) n = 52; // 比如只给了 2 张不同的牌,重复填充到 52 张 string card1 = "SA"; string card2 = "DQ"; for(int i=0; i<26; ++i) current_cards.push_back(card1); for(int i=0; i<26; ++i) current_cards.push_back(card2); shuffle(current_cards.begin(), current_cards.end(), g); } else { // 测试点 10: n=52, 完全随机 (有重复,无序) n = 52; // 随机抽取,允许重复 (抽样放回) uniform_int_distribution<int> dist(0, 51); for(int i=0; i<n; ++i) { current_cards.push_back(deck[dist(g)]); } } // --- 写入输入文件 --- fin << n << endl; for (const string& c : current_cards) { fin << c << endl; } // --- 计算答案并写入输出文件 --- int ans = solve(n, current_cards); fout << ans << endl; // 关闭文件 fin.close(); fout.close(); cout << "Generated Case " << id << ": n=" << n << " -> Ans: " << ans << endl; } cout << "全部数据生成完毕!" << endl; return 0; }数据设计详解
- 测试点 1 (n=1, A)
- 最基础的情况,只有一张牌。
- 测试点 2-4 (n<=52, A)
- 特点:输入的牌互不相同(无重复)。
- Case 4: 且互不相同,意味着输入了一副完整的牌,答案应该是 0。这是为了测试程序是否正确处理了“不需要借牌”的情况。
- 测试点 5-7 (n<=52, B)
- 特点:输入的牌是按照题目描述的特定顺序排序的(点数 A-K,花色 D-S)。
- 虽然这对
set去重做法没有影响,但如果选手尝试手动去重(比如只比较相邻元素),这种数据能保证其正确性;同时也符合题目给定的特殊性质。 - Case 7:有序的完整 52 张牌。
- 测试点 8 (无特殊性质)
- ,包含少量重复。测试基本的去重逻辑。
- 测试点 9 (无特殊性质,高重复)
- ,但实际上只由 2 种牌重复填充而成。
- 答案应该是 50。这是测试去重逻辑是否健壮的极端情况(比如手写去重逻辑可能有 bug)。
- 测试点 10 (随机)
- ,完全随机抽取(有放回)。这是一般情况的压力测试。
使用方法
- 保存代码为
gen.cpp。 - 编译运行。
- 生成的
1.in~10.in即为输入数据,1.out~10.out为对应的正确输出。
- 测试点 1 (n=1, A)
-
0
这是一份标准的 AC 代码。
代码核心逻辑
这道题的核心是去重。虽然输入了 张牌,但其中可能有重复的。我们需要统计不重复的牌有多少张(记为 ),然后用总数 52 减去 即为答案。
使用 C++ STL 中的
std::set(集合)容器是最简单、最标准的方法,因为集合会自动过滤掉重复的元素。标准 C++ 代码
#include <iostream> #include <string> #include <set> // 引入集合容器头文件 using namespace std; int main() { // 1. 优化输入输出效率(好习惯) ios::sync_with_stdio(false); cin.tie(0); int n; if (!(cin >> n)) return 0; // 2. 定义一个存储字符串的集合 // set 的特性:插入重复元素时会自动忽略,保证容器内元素唯一 set<string> cards; for (int i = 0; i < n; i++) { string s; cin >> s; // 3. 将牌插入集合 // 比如输入两次 "DQ",集合里只会保留一个 "DQ" cards.insert(s); } // 4. 计算答案 // cards.size() 就是去重后手里的牌数 // 需要借的牌数 = 52 - 手里有的不同牌数 cout << 52 - cards.size() << endl; return 0; }复杂度分析
- 时间复杂度:
set的插入操作是 ,总共插入 次,复杂度为 。由于 ,这个开销微乎其微。 - 空间复杂度:,用于存储字符串。
替代方案(如果不熟悉
set)如果你还没学过
set,也可以使用sort+unique或者简单的排序后遍历的方法:- 把所有字符串读入
vector<string>或数组。 - 使用
sort排序,这样相同的牌会挨在一起。 - 遍历数组,如果
a[i] != a[i-1],说明是新牌,计数器加 1。
- 时间复杂度:
-
0
你好!我是你的 OI 教练。
这道题(CSP-J 2024 T1 扑克牌)是刚刚过去不久的 2024 年入门组第一题。作为签到题,它的核心考察点非常明确:去重(Deduplication)。
题目并不需要你真的去模拟借牌的过程,而是考察你能不能算出“手里究竟有多少张不一样的牌”。
下面我为你梳理一下解题思路:
1. 核心逻辑分析
- 目标:凑齐一副完整的 52 张牌。
- 现状:手里有 张牌,但这些牌里可能有重复的(比如两张“方片Q”)。
- 关键点:重复的牌对于凑齐一副牌是没有额外帮助的。只有不重复的牌才算有效。
- 公式:
2. 样例解析(避坑指南)
让我们看看 样例 2:
4 DQ H3 DQ DT输入了 4 张牌,分别是:方片Q、红桃3、方片Q、方片10。 注意到了吗?方片Q 出现了两次。 所以在计算时,有效牌只有 3 张(DQ, H3, DT)。 答案应该是 。
如果你直接计算 ,输出 ,那就掉进坑里了。
3. 实现思路(两种方法)
方法一:使用 STL 的
std::set(最推荐,最快)C++ 的标准库提供了一个容器叫
set(集合),它的特性就是自动去重。你往里面塞 100 个 "DQ",它里面也只会存 1 个 "DQ"。步骤:
- 定义一个
set<string>。 - 读入 个字符串,每读一个就
insert进集合。 - 最后集合的大小
size()就是你拥有的不同牌的数量。 - 输出
52 - size()。
方法二:布尔数组标记(传统方法)
如果你不想用
set,也可以用一个map或者标记数组。- 定义
map<string, bool> have;。 - 每读入一个字符串
s:- 如果
have[s]是false,说明这张牌第一次出现,计数器cnt++,并标记have[s] = true。 - 如果
have[s]已经是true,说明之前拿过了,直接忽略。
- 如果
- 输出
52 - cnt。
4. 代码结构框架(基于
set)#include <iostream> #include <string> #include <set> // 必须包含这个头文件 using namespace std; int main() { int n; cin >> n; // 定义一个存放字符串的集合 set<string> cards; for(int i = 0; i < n; i++) { string s; cin >> s; // 将牌插入集合,重复的牌会自动被忽略 cards.insert(s); } // cards.size() 会返回集合中元素的个数(即去重后的数量) // 还需要借几张? cout << 52 - cards.size() << endl; return 0; }这道题非常简单,主要是考察你是否细心(注意去重)以及是否会使用基本的 STL 工具。加油!
- 1
信息
- ID
- 15764
- 时间
- 1000ms
- 内存
- 32MiB
- 难度
- 1
- 标签
- 递交数
- 7
- 已通过
- 3
- 上传者