当前位置: 首页 > news >正文

洛谷 P11227 [CSP-J 2024] 扑克牌

洛谷 P11227 [CSP-J 2024] 扑克牌

恩师:hnjzsyjyj

一、题目介绍:认识扑克牌问题

1.1 题目背景

扑克牌是一种广泛流传的娱乐工具,标准扑克牌通常包含 52 张牌,分为 4 种花色(黑桃、红桃、梅花、方块)和 13 种点数(A、2-10、J、Q、K)。本题以扑克牌为背景,考察对数据去重和计数的基本编程能力,是 2024 年 CSP-J(非专业级软件能力认证)的入门级题目。

1.2 题目描述

题目可以简化为:

  • 标准扑克牌有 52 张不同的牌
  • 输入 n 张扑克牌(可能有重复)
  • 计算这些牌中不同牌的数量(去重后的数量)
  • 最终输出 52 减去这个数量,即 "还缺少多少种不同的牌"

简单来说,就是要找出输入的牌中有多少种是独特的,然后计算完整的 52 张牌中还缺少多少种。

1.3 输入输出格式

  • 输入:第一行是一个整数 n(表示输入的牌的数量),接下来 n 行每行是一张扑克牌的表示(如 "SA" 表示黑桃 A,"HJ" 表示红桃 J 等)
  • 输出:一个整数,表示 52 张标准牌中缺少的牌的种类数

1.4 示例说明

以示例输入为例:

plaintext

5
SA
SA
HK
DQ
CQ

  • 输入了 5 张牌,但其中 "SA" 出现了 2 次
  • 去重后不同的牌有 4 种:SA、HK、DQ、CQ
  • 因此缺少的牌的种类数为 52-4=48
  • 输出结果:48

二、解题思路:用映射表实现去重计数

2.1 核心问题分析

本题的核心是解决两个问题:

  1. 如何判断一张牌是否已经出现过(去重)
  2. 如何统计不同牌的总数

这两个问题可以通过映射表(map) 数据结构完美解决,因为 map 可以存储键值对,并且键是唯一的,非常适合用于去重和计数场景。

2.2 解题步骤

  1. 读取输入的牌的数量 n
  2. 创建一个映射表(map)用于存储出现过的牌
  3. 循环读取 n 张牌:
    • 对于每张牌,检查它是否已经在映射表中
    • 如果不在,就将它加入映射表,并更新不同牌的计数
  4. 计算 52 减去不同牌的总数,得到缺少的牌的种类数
  5. 输出结果

2.3 为什么用 map?

在 C++ 中,map 是一种关联容器,它的特点是:

  • 存储的元素是键值对(key-value)
  • 键(key)是唯一的,不会重复
  • 可以快速判断一个键是否存在(平均时间复杂度为 O (log n))

这些特性正好符合本题的需求:我们需要记录哪些牌已经出现过,并且统计不同牌的数量。使用 map 可以让代码更简洁、高效。

三、代码解析:逐行理解程序逻辑

下面我们来详细分析用户提供的代码,看看它是如何实现上述解题思路的:

cpp

运行

#include<bits/stdc++.h>
using namespace std;
map<string,int> m;
string a;
int n,ans;
int main() {cin>>n;while(n--){cin>>a;if(!m[a])m[a]=++ans;}cout<<52-ans;return 0;
}

3.1 头文件与命名空间

cpp

运行

#include<bits/stdc++.h>
using namespace std;

  • #include<bits/stdc++.h>:这是 C++ 的万能头文件,包含了所有标准库,包括我们需要的 map 容器
  • using namespace std;:使用标准命名空间,这样我们可以直接使用mapcincout等,而不必添加std::前缀

3.2 全局变量定义

cpp

运行

map<string,int> m;
string a;
int n,ans;

定义了 4 个全局变量:

  • map<string,int> m:一个映射表,键是 string 类型(存储牌的表示),值是 int 类型(存储该牌首次出现的序号)
  • string a:用于临时存储当前读取的牌
  • int n:存储输入的牌的数量
  • int ans:用于统计不同牌的数量,初始值为 0(全局变量默认初始值)

3.3 主函数入口

cpp

运行

int main() {// 程序逻辑return 0;
}

main()函数是程序的入口,所有代码都在其中执行。

3.4 读取牌的数量

cpp

运行

cin>>n;

通过cin读取输入的第一个整数 n,即牌的总数量。

3.5 循环读取每张牌

cpp

运行

while(n--){// 处理每张牌的逻辑
}

这是一个while循环,会执行 n 次(每次循环后 n 的值减 1,直到 n 变为 0),用于读取 n 张牌并处理。

3.6 处理单张牌

cpp

运行

cin>>a;
if(!m[a])m[a]=++ans;

这两行是程序的核心逻辑:

  1. cin>>a:读取一张牌,存储到字符串变量 a 中
  2. if(!m[a])m[a]=++ans
    • m[a]:访问 map 中键为 a 的元素的值
    • 如果该牌之前没有出现过(m[a]的值为 0),则!m[a]为真
    • 此时执行m[a]=++ans:将该牌加入 map,并将 ans 的值加 1 后赋给它

这个逻辑的巧妙之处在于:只有当牌第一次出现时,才会更新 ans 的值,从而实现了去重计数的功能。

3.7 输出结果

cpp

运行

cout<<52-ans;

循环结束后,ans 的值就是不同牌的数量。输出 52 减去 ans,即缺少的牌的种类数。

四、代码执行过程演示

为了更直观地理解代码的执行过程,我们以示例输入为例,分步演示程序的运行:

示例输入:

plaintext

5
SA
SA
HK
DQ
CQ

  1. 初始状态:m 为空,ans=0,n=5

  2. 第一次循环(n=5→4)

    • 读取 a="SA"
    • 检查 m ["SA"],此时 map 中没有 "SA",m ["SA"]=0
    • 执行 m ["SA"]=++ans:ans 变为 1,map 中添加 "SA"→1
    • 当前 map:{"SA":1},ans=1
  3. 第二次循环(n=4→3)

    • 读取 a="SA"
    • 检查 m ["SA"]=1(非 0)
    • 不执行任何操作
    • 当前 map:{"SA":1},ans=1
  4. 第三次循环(n=3→2)

    • 读取 a="HK"
    • 检查 m ["HK"]=0
    • 执行 m ["HK"]=++ans:ans 变为 2,map 中添加 "HK"→2
    • 当前 map:{"SA":1, "HK":2},ans=2
  5. 第四次循环(n=2→1)

    • 读取 a="DQ"
    • 检查 m ["DQ"]=0
    • 执行 m ["DQ"]=++ans:ans 变为 3,map 中添加 "DQ"→3
    • 当前 map:{"SA":1, "HK":2, "DQ":3},ans=3
  6. 第五次循环(n=1→0)

    • 读取 a="CQ"
    • 检查 m ["CQ"]=0
    • 执行 m ["CQ"]=++ans:ans 变为 4,map 中添加 "CQ"→4
    • 当前 map:{"SA":1, "HK":2, "DQ":3, "CQ":4},ans=4
  7. 循环结束:输出 52-4=48,与预期结果一致

通过这个过程可以清晰地看到,代码如何通过 map 实现了去重,并正确统计了不同牌的数量。

五、map 容器的工作原理

5.1 map 的基本概念

map 是 C++ 标准库中的一种关联容器,它按照键(key)的顺序存储键值对(key-value pairs)。map 中的键是唯一的,这意味着不能有两个元素拥有相同的键。

在本题中,我们使用 map<string, int>,表示键是字符串类型(存储牌的标识),值是整数类型(存储该牌首次出现的序号)。

5.2 为什么 m [a] 初始值为 0?

在 C++ 中,当我们访问 map 中不存在的键时,map 会自动插入该键,并将其对应的值初始化为该类型的默认值:

  • 对于 int 类型,默认值是 0
  • 对于 string 类型,默认值是空字符串

这就是为什么当我们第一次访问 m ["SA"] 时,它的值是 0,我们可以通过if(!m[a])来判断该牌是否是第一次出现。

5.3 ++ans 的作用

m[a] = ++ans这个操作有两个作用:

  1. ++ans:将计数器 ans 的值加 1,统计新出现的牌
  2. m[a] = ...:将该牌添加到 map 中,并赋值为当前 ans 的值(表示这是第几个出现的新牌)

这样,当再次遇到相同的牌时,m [a] 的值已经不是 0 了,不会再被计数。

5.4 为什么不用数组?

可能有同学会问:为什么不用数组来存储牌的出现情况?主要有两个原因:

  1. 牌的标识是字符串(如 "SA"、"HK"),而数组的下标只能是整数
  2. 牌的种类是固定的 52 种,但用数组需要建立字符串到整数的映射,反而更麻烦

使用 map 可以直接用牌的字符串作为键,更加直观和方便。

六、易错点分析:这些坑要注意

6.1 对 map 的访问会自动插入元素

初学者容易忽略的一点是:在 map 中访问一个不存在的键时,会自动插入该键并赋予默认值。例如:

cpp

运行

map<string, int> m;
if (m["SA"] == 0) {// 即使原本没有"SA",这里也会插入"SA"并赋值0
}

在本题的代码中,这个特性恰好被合理利用,但在其他场景下可能导致意外的结果。

6.2 全局变量与局部变量的区别

示例代码中将 ans 定义为全局变量,默认初始值为 0。如果将 ans 定义为 main 函数内的局部变量,必须显式初始化:

cpp

运行

int main() {int ans = 0; // 必须显式初始化,否则值是不确定的// ...
}

否则 ans 的初始值是随机的,会导致统计结果错误。

6.3 输入输出格式错误

虽然本题的输入输出格式简单,但仍需注意:

  • 第一行是 n,之后 n 行每行是一张牌
  • 输出是一个整数,不需要其他文字

6.4 字符串比较的问题

在 C++ 中,字符串的比较是按字典顺序进行的,这正好符合我们的需求。例如 "SA" 和 "SA" 会被判定为相等,而 "SA" 和 "SB" 会被判定为不相等。

但需要注意输入的牌的格式是否统一,例如是否区分大小写(题目中通常会说明牌的表示方法)。

6.5 边界情况处理

需要注意一些边界情况:

  • n=0:输入 0 张牌,此时输出 52
  • n=52 且所有牌都不同:输出 0
  • n>52:可能有很多重复的牌,只需统计不同的数量

示例代码已经正确处理了这些边界情况,因为:

  • 当 n=0 时,循环不执行,ans=0,输出 52-0=52
  • 当所有牌都不同时,ans=52,输出 52-52=0

七、代码优化与拓展

7.1 使用 unordered_map 提高效率

在 C++ 中,map 的底层实现是红黑树,查找元素的时间复杂度是 O (log n)。如果 n 非常大,可以使用 unordered_map,它的底层实现是哈希表,查找元素的平均时间复杂度是 O (1):

cpp

运行

#include<bits/stdc++.h>
using namespace std;
unordered_map<string,int> m; // 改为unordered_map
string a;
int n,ans;
int main() {cin>>n;while(n--){cin>>a;if(!m[a])m[a]=++ans;}cout<<52-ans;return 0;
}

对于本题规模的输入,两者效率差异不大,但了解这种优化方法有助于解决更大规模的问题。

7.2 使用 set 实现去重

除了 map,我们还可以使用 set(集合)来实现去重功能,因为 set 中也不能有重复元素:

cpp

运行

#include<bits/stdc++.h>
using namespace std;
set<string> s;
string a;
int n;
int main() {cin>>n;while(n--){cin>>a;s.insert(a); // insert方法会自动忽略重复元素}cout<<52-s.size(); // size()返回集合中元素的数量return 0;
}

这种方法代码更简洁,直接利用 set 的特性实现去重,然后通过 size () 方法获取不同元素的数量。

7.3 手动实现哈希表(拓展学习)

对于学习深入的同学,可以尝试手动实现一个简单的哈希表来解决这个问题,这有助于理解 map 和 set 的底层原理。

基本思路是:

  1. 创建一个足够大的数组
  2. 设计一个哈希函数,将牌的字符串转换为数组下标
  3. 使用开链法或线性探测法处理哈希冲突
  4. 记录哪些下标被使用过,统计总数

这是一个很好的练习,可以加深对数据结构的理解。

八、知识点总结:从扑克牌问题学到的编程思想

8.1 去重计数的通用方法

本题本质上是一个去重计数问题,这类问题的通用解决方法有:

  • 使用集合(set)存储元素,自动去重,然后获取集合大小
  • 使用映射表(map)存储元素是否出现,通过判断值来计数
  • 先排序再遍历,通过比较相邻元素来计数

在 C++ 中,使用 set 或 map 是最简洁高效的方法。

8.2 关联容器的应用

本题展示了关联容器(map)的典型应用场景:

  • 需要判断元素是否存在
  • 需要统计不同元素的数量
  • 需要根据键快速查找

掌握关联容器的使用是 C++ 编程的重要基础,在很多实际问题中都有应用。

8.3 问题简化的思维

解决编程问题时,经常需要将复杂问题简化:

  • 本题将 "计算缺少的牌的种类数" 简化为 "52 减去已出现的不同牌的数量"
  • 而 "已出现的不同牌的数量" 又可以通过去重计数来解决

这种化繁为简的思维方式是解决复杂问题的关键。

8.4 细节处理的重要性

虽然本题代码简短,但包含了很多细节处理:

  • 全局变量的默认初始化
  • map 访问不存在的键时的自动插入特性
  • 前缀 ++ 运算符的使用时机

这些细节处理体现了编程的严谨性,也是保证程序正确性的关键。

九、PPT 展示建议

如果用这篇内容制作 PPT,可以按照以下结构组织,突出重点,方便讲解:

  1. 封面页

    • 标题:洛谷 P11227 [CSP-J 2024] 扑克牌
    • 副标题:使用 map 实现去重计数
    • 背景图:扑克牌相关图片
  2. 题目介绍页

    • 简洁描述题目要求
    • 列出输入输出格式
    • 展示 1-2 个示例(用表格形式展示输入和输出)
  3. 核心问题分析页

    • 提炼问题本质:去重计数
    • 说明为什么需要去重
    • 展示最终计算方式:52 - 不同牌的数量
  4. 解题思路页

    • 用流程图展示解题步骤
    • 突出 map 的作用
    • 说明每一步的操作
  5. 代码框架页

    • 展示完整代码
    • 用不同颜色标注关键部分
    • 简要说明代码结构
  6. 代码解析页(分 2-3 页):

    • 逐行解释代码含义
    • 重点讲解 map 的使用和条件判断
    • 说明 ans 变量的作用
  7. 执行过程演示页

    • 以示例输入为例
    • 分步展示循环执行过程
    • 用图示展示 map 的变化和 ans 的取值
  8. map 工作原理页

    • 简单介绍 map 的特性
    • 解释为什么 m [a] 初始值为 0
    • 展示 map 如何实现去重
  9. 易错点与优化页

    • 列出常见错误类型
    • 展示优化方法(如使用 set)
    • 对比不同方法的优缺点
  10. 总结与拓展页

    • 提炼核心知识点
    • 推荐相关练习题目
    • 总结去重计数问题的解决方法

十、写在最后

扑克牌问题虽然是一道简单的入门级编程题,但它很好地展示了关联容器在实际问题中的应用。通过解决这道题,我们不仅学会了具体的解题方法,更重要的是掌握了去重计数这一常见问题的解决思路,理解了 map 等关联容器的工作原理。

在编程学习中,这类看似简单的题目往往蕴含着重要的基础知识和思维方法。它们是构建更复杂程序的基石,值得我们深入理解和掌握。

记住,优秀的程序员不仅能写出正确的代码,更能理解代码背后的原理,知道为什么这样写,以及有没有更好的写法。通过不断思考和优化,我们的编程能力才能不断提升。

希望这篇博客能帮助你更好地理解扑克牌问题和关联容器的应用。如果有任何疑问或建议,欢迎在评论区留言讨论!

http://www.dtcms.com/a/304808.html

相关文章:

  • 微算法科技(NASDAQ:MLGO)应用区块链联邦学习(BlockFL)架构,实现数据的安全传输
  • Ika Network 正式发布,让 Sui 智能合约可管理跨链资产
  • 格雷码的应用场景
  • 光环云在2025WAIC联合发布“AI for SME 全球普惠发展倡议”
  • 银行回单识别和发票识别相结合的应用场景及技术方案
  • 20250729-day23
  • 【Mac版】Linux 入门命令行快捷键+联想记忆
  • RDD的checkpoint检查点机制(Checkpoint 与 Persist 的区别)
  • 负载均衡、算法/策略
  • linux实战--日志管理
  • 数字ic后端设计从入门到精通13(含fusion compiler, tcl教学)全定制版图设计
  • 【嵌入式电机控制#17】电流环(四):电流闭环控制
  • 汽车品牌如何用直播“开出去”?从展厅到售后,一站式解决方案
  • 智慧园区系统引领未来:一场科技与生活的完美融合
  • 微信小程序无法构建npm,可能是如下几个原因
  • linux内核报错汇编分析
  • C++学习之继承
  • 【IQA技术专题】纹理相似度图像评价指标DISTS
  • 编写一个markdown文本编辑器工具
  • 7月29号打卡
  • 无需反复登录!当贝AI聚合通义Qwen3-235B等14大模型
  • 大文件的切片上传和断点续传前后端(Vue+node.js)具体实现
  • JetBrains IDE插件开发及发布
  • java导入pdf(携带动态表格,图片,纯java不需要模板)
  • 15K的Go开发岗,坐标北京
  • 第七章 MCP协议
  • Wndows Docker Desktop-Unexpected WSL error错误
  • 报告研读——80页数据资产化实践指南报告-2024【附全文阅读】
  • 天铭科技×蓝卓 | “1+2+N”打造AI驱动的汽车零部件行业智能工厂
  • 为什么全景渲染更耗时?关键因素解析