3 条题解

  • 0
    @ 2025-11-28 12:05:38

    这是一份功能完整的数据生成器,专门用于生成 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. 测试点 1 (n=1, A)
      • 最基础的情况,只有一张牌。
    2. 测试点 2-4 (n<=52, A)
      • 特点:输入的牌互不相同(无重复)。
      • Case 4n=52n=52 且互不相同,意味着输入了一副完整的牌,答案应该是 0。这是为了测试程序是否正确处理了“不需要借牌”的情况。
    3. 测试点 5-7 (n<=52, B)
      • 特点:输入的牌是按照题目描述的特定顺序排序的(点数 A-K,花色 D-S)。
      • 虽然这对 set 去重做法没有影响,但如果选手尝试手动去重(比如只比较相邻元素),这种数据能保证其正确性;同时也符合题目给定的特殊性质。
      • Case 7:有序的完整 52 张牌。
    4. 测试点 8 (无特殊性质)
      • n=20n=20,包含少量重复。测试基本的去重逻辑。
    5. 测试点 9 (无特殊性质,高重复)
      • n=52n=52,但实际上只由 2 种牌重复填充而成。
      • 答案应该是 50。这是测试去重逻辑是否健壮的极端情况(比如手写去重逻辑可能有 bug)。
    6. 测试点 10 (随机)
      • n=52n=52,完全随机抽取(有放回)。这是一般情况的压力测试。

    使用方法

    1. 保存代码为 gen.cpp
    2. 编译运行。
    3. 生成的 1.in ~ 10.in 即为输入数据,1.out ~ 10.out 为对应的正确输出。
    • 0
      @ 2025-11-28 12:03:42

      这是一份标准的 AC 代码。

      代码核心逻辑

      这道题的核心是去重。虽然输入了 nn 张牌,但其中可能有重复的。我们需要统计不重复的牌有多少张(记为 cntcnt),然后用总数 52 减去 cntcnt 即为答案。

      使用 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 的插入操作是 O(logN)O(\log N),总共插入 NN 次,复杂度为 O(NlogN)O(N \log N)。由于 N52N \le 52,这个开销微乎其微。
      • 空间复杂度O(N)O(N),用于存储字符串。

      替代方案(如果不熟悉 set

      如果你还没学过 set,也可以使用 sort + unique 或者简单的排序后遍历的方法:

      1. 把所有字符串读入 vector<string> 或数组。
      2. 使用 sort 排序,这样相同的牌会挨在一起。
      3. 遍历数组,如果 a[i] != a[i-1],说明是新牌,计数器加 1。
      • 0
        @ 2025-11-28 12:02:17

        你好!我是你的 OI 教练。

        这道题(CSP-J 2024 T1 扑克牌)是刚刚过去不久的 2024 年入门组第一题。作为签到题,它的核心考察点非常明确:去重(Deduplication)

        题目并不需要你真的去模拟借牌的过程,而是考察你能不能算出“手里究竟有多少张不一样的牌”。

        下面我为你梳理一下解题思路:

        1. 核心逻辑分析

        • 目标:凑齐一副完整的 52 张牌。
        • 现状:手里有 nn 张牌,但这些牌里可能有重复的(比如两张“方片Q”)。
        • 关键点:重复的牌对于凑齐一副牌是没有额外帮助的。只有不重复的牌才算有效。
        • 公式需要借的牌数=52手里已经有的【不同】牌的数量\text{需要借的牌数} = 52 - \text{手里已经有的【不同】牌的数量}

        2. 样例解析(避坑指南)

        让我们看看 样例 2

        4
        DQ
        H3
        DQ
        DT
        

        输入了 4 张牌,分别是:方片Q、红桃3、方片Q、方片10。 注意到了吗?方片Q 出现了两次。 所以在计算时,有效牌只有 3 张(DQ, H3, DT)。 答案应该是 523=4952 - 3 = 49

        如果你直接计算 52n52 - n,输出 4848,那就掉进坑里了。

        3. 实现思路(两种方法)

        方法一:使用 STL 的 std::set(最推荐,最快)

        C++ 的标准库提供了一个容器叫 set(集合),它的特性就是自动去重。你往里面塞 100 个 "DQ",它里面也只会存 1 个 "DQ"。

        步骤

        1. 定义一个 set<string>
        2. 读入 nn 个字符串,每读一个就 insert 进集合。
        3. 最后集合的大小 size() 就是你拥有的不同牌的数量。
        4. 输出 52 - size()

        方法二:布尔数组标记(传统方法)

        如果你不想用 set,也可以用一个 map 或者标记数组。

        1. 定义 map<string, bool> have;
        2. 每读入一个字符串 s
          • 如果 have[s]false,说明这张牌第一次出现,计数器 cnt++,并标记 have[s] = true
          • 如果 have[s] 已经是 true,说明之前拿过了,直接忽略。
        3. 输出 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
        上传者