CSP-J 2024 第二轮 T1 题解

温馨提示:此次比赛中NOI布置了许多Hack数据,没有通过的不用伤心,我也没通过(悲)

看题:

题目描述

小 P 从同学小 Q 那儿借来一副 nn 张牌的扑克牌。

本题中我们不考虑大小王,此时每张牌具有两个属性:花色和点数。花色共有 44 种:方片、草花、红桃和黑桃。点数共有 1313 种,从小到大分别为 A23456789TJQKA23456789TJQK。注意:点数 1010 在本题中记为 TT。

我们称一副扑克牌是完整的,当且仅当对于每一种花色和每一种点数,都恰好有一张牌具有对应的花色和点数。由此,一副完整的扑克牌恰好有 4×13=524×13=52 张牌。以下图片展示了一副完整的扑克牌里所有的 52 张牌。

小 P 借来的牌可能不是完整的,为此小 P 准备再向同学小 S 借若干张牌。可以认为小 S 每种牌都有无限张,因此小 P 可以任意选择借来的牌。小 P 想知道他至少得向小 S 借多少张牌,才能让从小 S 和小 Q 借来的牌中,可以选出 5252 张牌构成一副完整的扑克牌。

为了方便你的输入,我们使用字符 DD 代表方片,字符 CC 代表草花,字符 HH 代表红桃,字符 SS 代表黑桃,这样每张牌可以通过一个长度为 22 的字符串表示,其中第一个字符表示这张牌的花色,第二个字符表示这张牌的点数,例如 CACA 表示草花 AA,STST 表示黑桃 TT(黑桃 10)。

输入格式

输入的第一行包含一个整数 nn 表示牌数。

接下来 nn 行:

每行包含一个长度为 22 的字符串描述一张牌,其中第一个字符描述其花色,第二个字符描述其点数。

输出格式

输出一行一个整数,表示最少还需要向小 S 借几张牌才能凑成一副完整的扑克牌。

输入输出样例

输入样例#1:                 输出样例#1:

1                                     51

SA

测试点编号 n≤n≤ 特殊性质
11 11 A
2∼42∼4 5252 A
5∼75∼7 5252 B
8∼108∼10 5252

特殊性质 A:保证输入的 nn 张牌两两不同。

特殊性质 B:保证所有牌按照点数从小到大依次输入,点数相同时按照方片、草花、红桃、黑桃的顺序依次输入。

详见洛谷:P11227 [CSP-J 2024] 扑克牌(官方数据) - 洛谷 | 计算机科学教育新生态

解题思路

所有解题的第一步是你要理解题目,题目的意思就是给你N张扑克牌,问你除了这N张以外,其余的52-N张扑克牌数目,另外,如果这N张扑克牌里有重复的牌,你就不用算了。

所以,解题思路就是:

1.输入N

2.循环N,一边输入,一边记录/查重

3.用52减去记录数组的长度

代码

注意,这不是AC代码!

我们来看一下这个代码:

/*C++11*/
#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

int cardToIndex(char suit, char value) {
    int suitIndex = suit == 'D' ? 0 : suit == 'C' ? 1 : suit == 'H' ? 2 : 3;
    int valueIndex = value == 'T' ? 10 : value == 'J' ? 11 : value == 'Q' ? 12 : value == 'K' ? 13 : value - 'A' + 1;
    return 13 * suitIndex + valueIndex;
}

int main() {
    int n;
    cin >> n;

    unordered_map<int, bool> cardExists;
    for (int i = 0; i < n; ++i) {
        char suit, value;
        cin >> suit >> value;
        int index = cardToIndex(suit, value);
        cardExists[index] = true;
    }

    int missingCards = 0;
    for (int suit = 0; suit < 4; ++suit) {
        for (int value = 1; value <= 13; ++value) {
            int index = 13 * suit + value - 1;
            if (!cardExists[index]) {
                ++missingCards;
            }
        }
    }

    cout << missingCards << endl;
    return 0;
}

这是一个非常复杂(多此一举)的代码(我写的),在比赛中,这段代码只能拿10分,唯一原因就是Hack数据和算法漏洞,你可以找一找BUG

下面是AC的代码

#include <bits/stdc++.h>
using namespace std;
int main() 
{
	set <string> S;
	int n;
    cin >> n;
	for (int i = 1;i <= n;i++)
    {
		string s;
		cin >> s;
		S.insert(s);
	}
	cout << 52 - S.size() << endl;
	return 0;
}

这段代码就非常符合我们的解题思路,详见分析

分析 

两段代码的主要区别在于它们处理问题的方法。第一段代码尝试通过映射每张牌到一个唯一的索引,并检查哪些牌是缺失的,而第二段代码使用了set来存储输入的牌,并直接计算缺失的牌数。

第一段代码可能只能得到10分的原因可能是因为它没有正确地处理所有的测试用例。具体来说,可能存在以下几个问题:

  1. 边界条件处理:可能没有正确处理点数为'T'(10)的情况,或者没有考虑到'A'到'K'的映射。
  2. 效率问题:使用unordered_map可能在某些情况下效率不如set,尤其是在这种只需要检查唯一性的场景下。
  3. 代码复杂度:代码可能过于复杂,导致在某些测试用例下运行时间过长或者出现错误。

而第二段代码使用了set来存储牌的字符串表示,这种方法简单且直接。set自动去除了重复的元素,并且保证了元素的唯一性。在读取所有牌之后,直接输出52减去set的大小即可得到缺失的牌数。这种方法的优点是:

  1. 简单高效:使用set可以直接利用其特性来保证牌的唯一性,代码简洁且效率高。
  2. 自动去重set在插入时会自动处理重复的元素,不需要额外的逻辑来检查是否已经存在。

心要静,手莫慌,先分析,后做题