温馨提示:此次比赛中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分的原因可能是因为它没有正确地处理所有的测试用例。具体来说,可能存在以下几个问题:
- 边界条件处理:可能没有正确处理点数为'T'(10)的情况,或者没有考虑到'A'到'K'的映射。
- 效率问题:使用
unordered_map
可能在某些情况下效率不如set
,尤其是在这种只需要检查唯一性的场景下。 - 代码复杂度:代码可能过于复杂,导致在某些测试用例下运行时间过长或者出现错误。
而第二段代码使用了set
来存储牌的字符串表示,这种方法简单且直接。set
自动去除了重复的元素,并且保证了元素的唯一性。在读取所有牌之后,直接输出52减去set
的大小即可得到缺失的牌数。这种方法的优点是:
- 简单高效:使用
set
可以直接利用其特性来保证牌的唯一性,代码简洁且效率高。 - 自动去重:
set
在插入时会自动处理重复的元素,不需要额外的逻辑来检查是否已经存在。
心要静,手莫慌,先分析,后做题