UVA 340 这道题,也就是 Master-Mind Hints,你是不是觉得有点摸不着头脑?别担心,很多人初次接触时都会有类似的感觉。它其实是模拟了一个经典的猜数字游戏,叫做 Mastermind(也叫“智力魔方”或“猜数字”),你需要根据提示来判断你的猜测有多接近正确答案。
📖 本文目录
Mastermind 游戏规则速览
在深入 UVA 340 之前,我们先快速了解一下 Mastermind 游戏的基本规则。这个游戏通常由两名玩家参与:一名是“密码设置者”,另一名是“猜码者”。
- 设置密码: 密码设置者会偷偷设定一个由特定数量(比如四位)和特定颜色(比如六种颜色)组成的密码序列。序列中的颜色可以重复。
- 进行猜测: 猜码者每次会尝试猜一个密码序列。
- 给出提示: 密码设置者会根据猜码者的猜测,给出两种提示:
- 黑棋子(Black Pegs): 表示猜对的数字(或颜色),并且位置也正确。
- 白棋子(White Pegs): 表示猜对的数字(或颜色),但是位置不正确。
- 重复猜测: 猜码者根据提示继续猜测,直到猜对密码或用完所有猜测次数。
UVA 340 就是让你来扮演这个“密码设置者”的角色,给出一个秘密序列和一系列猜测序列,然后你需要计算出每个猜测对应的黑棋子和白棋子数量。
UVA 340 题目要求解析
这道题会给你一个秘密序列(Secret Code),以及一系列的猜测序列(Guess Code)。你的任务是对于每一个猜测序列,计算出它相对于秘密序列有多少个“强匹配”和多少个“弱匹配”。题目中的数字范围通常是 1 到 9。
用更直白的话说:
- 强匹配 (Strong Match):对应 Mastermind 游戏中的“黑棋子”。这意味着猜测的数字和秘密序列中的数字值相同,且位置也相同。
- 弱匹配 (Weak Match):对应 Mastermind 游戏中的“白棋子”。这意味着猜测的数字和秘密序列中的数字值相同,但位置不同。这里有一个非常重要的条件:一个数字一旦被用于计算强匹配或弱匹配,就不能再被重复使用。

理解这个“不能重复使用”的规则是解题的关键,也是很多人容易出错的地方。
理解黑白棋子:强匹配与弱匹配
我们用一个表格来帮你更好地理解这两种匹配的差异:
| 特征 | 强匹配(黑棋子) | 弱匹配(白棋子) |
|---|---|---|
| 条件 | 数字值相同,且位置相同 | 数字值相同,但位置不同 |
| 优先级 | 优先计算 | 在强匹配计算完成后,对剩余数字进行计算 |
| 重复使用 | 一旦匹配成功,该数字在秘密序列和猜测序列中都不能再被用于其他匹配 | |
| 示例 | 秘密:1234,猜测:1567 -> 1个强匹配 (数字1) | 秘密:1234,猜测:4567 -> 1个弱匹配 (数字4) |
解题核心思路:分步计数法
要正确地计算强匹配和弱匹配,最稳妥的方法是采用“分步计数法”。这个方法可以有效避免重复计算和遗漏。
- 优先计算强匹配: 先找出所有值和位置都相同的数字。
- 排除已匹配的数字: 将这些已匹配的数字从秘密序列和猜测序列中“移除”或“标记”,确保它们不会再参与后续的弱匹配计算。
- 计算弱匹配: 对剩余的、未被标记的数字,统计它们在两个序列中出现的次数,找出值相同但位置不同的数字。
第一步:计算强匹配(黑棋子)
这一步相对简单,你需要遍历秘密序列和猜测序列,逐个比较对应位置的数字。
具体做法:
- 初始化
strong_matches = 0。 - 使用一个循环,从第一个数字到最后一个数字,同时检查秘密序列和猜测序列的第
i个位置。 - 如果
secret[i] == guess[i],那么strong_matches加 1。 - 为了避免这些数字在后续弱匹配计算中被重复使用,你可以将它们标记为已用(例如,将它们的值改为一个特殊标记,如 0,或者使用布尔数组
used_secret[]和used_guess[]来记录)。
示例说明:
假设:
- 秘密序列 (Secret):
1 2 3 4 - 猜测序列 (Guess):
1 3 2 4
- 比较
secret[0](1) 和guess[0](1):相同。strong_matches = 1。标记secret[0]和guess[0]为已用。
- 此时,秘密序列可视为
_ 2 3 4,猜测序列可视为_ 3 2 4(下划线表示已用)。
- 比较
secret[1](2) 和guess[1](3):不同。 - 比较
secret[2](3) 和guess[2](2):不同。 - 比较
secret[3](4) 和guess[3](4):相同。strong_matches = 2。标记secret[3]和guess[3]为已用。
- 此时,秘密序列可视为
_ 2 3 _,猜测序列可视为_ 3 2 _。
最终,strong_matches = 2。
第二步:计算弱匹配(白棋子)
这是整个解题过程中最容易出错的部分。在计算完强匹配并标记好已使用的数字之后,你才能开始计算弱匹配。
关键点:排除已匹配的数字
你必须确保用于计算弱匹配的数字,既没有在强匹配中被使用过,也没有在当前的弱匹配计算中被重复使用。
如何高效统计剩余数字:
一种非常高效且不易出错的方法是使用“频率数组”(或者叫做“计数数组”)。由于数字范围通常是 1 到 9,我们可以创建两个大小为 10 的数组(索引 0-9),分别用于统计秘密序列和猜测序列中未被强匹配使用的数字的出现频率。
具体做法:
- 初始化
weak_matches = 0。 - 创建两个频率数组,例如
secret_counts[10]和guess_counts[10],全部初始化为 0。 - 再次遍历秘密序列和猜测序列。
- 对于秘密序列中的每个数字
secret[i]:如果secret[i]未被标记为已用,则将secret_counts[secret[i]]加 1。 - 对于猜测序列中的每个数字
guess[i]:如果guess[i]未被标记为已用,则将guess_counts[guess[i]]加 1。
- 遍历数字 1 到 9(或 0 到 9,取决于题目范围)。对于每个数字
d:
weak_matches累加上min(secret_counts[d], guess_counts[d])。这意味着,如果秘密序列中有 3 个未用的数字 '5',猜测序列中有 2 个未用的数字 '5',那么它们可以贡献min(3, 2) = 2个弱匹配。
示例说明(接上一步):
- 秘密序列 (Secret):
_ 2 3 _(下划线表示已在强匹配中用过) - 猜测序列 (Guess):
_ 3 2 _
- 填充频率数组:
secret_counts:secret[1]是 2,未用 ->secret_counts[2] = 1secret[2]是 3,未用 ->secret_counts[3] = 1- 其他为 0
guess_counts:guess[1]是 3,未用 ->guess_counts[3] = 1guess[2]是 2,未用 ->guess_counts[2] = 1- 其他为 0
- 计算弱匹配:
- 对于数字 1:
min(secret_counts[1], guess_counts[1]) = min(0, 0) = 0 - 对于数字 2:
min(secret_counts[2], guess_counts[2]) = min(1, 1) = 1。weak_matches = 1。 - 对于数字 3:
min(secret_counts[3], guess_counts[3]) = min(1, 1) = 1。weak_matches = 1 + 1 = 2。 - 对于数字 4-9:都为 0。
最终,weak_matches = 2。
结合强匹配,最终结果是:强匹配 2 个,弱匹配 2 个。
完整示例:手把手带你计算
我们来一个更复杂的例子,包含重复数字,看看如何一步步计算。
假设:
- 秘密序列 (Secret):
6 1 1 2 - 猜测序列 (Guess):
1 1 2 6
| 步骤 | 秘密序列 (Secret) | 猜测序列 (Guess) | strong_matches | weak_matches | 备注 |
|---|---|---|---|---|---|
| 1. 强匹配 | 0 | 0 | |||
比较 s[0](6) vs g[0](1) | 6 1 1 2 | 1 1 2 6 | 0 | 0 | 不同 |
比较 s[1](1) vs g[1](1) | 6 (1) 1 2 | 1 (1) 2 6 | 1 | 0 | 相同!strong_matches = 1。将 s[1] 和 g[1] 标记为已用。 |
比较 s[2](1) vs g[2](2) | 6 1 (1) 2 | 1 1 (2) 6 | 1 | 0 | 不同 |
比较 s[3](2) vs g[3](6) | 6 1 1 (2) | 1 1 2 (6) | 1 | 0 | 不同 |
| 强匹配结果 | 1 | 0 | |||
| 2. 准备弱匹配 | 此时,序列中未使用的数字: | ||||
| 秘密序列剩余 | 6 _ 1 2 | (下划线代表 s[1] 已被强匹配使用) | |||
| 猜测序列剩余 | 1 _ 2 6 | (下划线代表 g[1] 已被强匹配使用) | |||
| 计算频率 | |||||
secret_counts | cnt[6]=1, cnt[1]=1, cnt[2]=1 | ||||
guess_counts | cnt[1]=1, cnt[2]=1, cnt[6]=1 | ||||
| 3. 弱匹配 | 0 | ||||
| 比较数字 1 | 1 | min(secret_counts[1], guess_counts[1]) = min(1,1) = 1 | |||
| 比较数字 2 | 1+1=2 | min(secret_counts[2], guess_counts[2]) = min(1,1) = 1 | |||
| 比较数字 6 | 2+1=3 | min(secret_counts[6], guess_counts[6]) = min(1,1) = 1 | |||
| 弱匹配结果 | 3 |
最终结果:强匹配 1 个,弱匹配 3 个。
常见的错误与避坑指南
- 重复数字的处理: 这是最大的陷阱。一个数字一旦被“用掉”,就不能再参与其他匹配。上面提到的分步计数和频率数组的方法就是为了解决这个问题。
- 错误做法: 有些人会先计算所有相同值的数字,然后减去强匹配。比如
秘密: 1122, 猜测: 1111。 - 强匹配:
s[0]=g[0](1),s[1]=g[1](1)。共 2 个强匹配。 - 如果只是简单统计:秘密中有两个 '1',猜测中有四个 '1'。如果直接用
min(2,4)=2,然后减去强匹配,会得到 0 个弱匹配,这是对的。 - 但如果
秘密: 1234, 猜测: 1156 - 强匹配:
s[0]=g[0](1)。1个强匹配。 - 剩余:秘密
_234,猜测_156。 - 如果直接统计 '1' 的数量:秘密中 '1' 有 1 个,猜测中 '1' 有 2 个。如果减去强匹配,会得到
(min(1,2) - 1) = 0个。但实际上,秘密中的 '1' 已经用掉了,猜测中的第二个 '1' 无法匹配。 - 正确做法: 严格按照先计算强匹配,然后将已匹配的数字“移除”或“标记”,最后再统计剩余数字的频率来计算弱匹配。
- 计数顺序的重要性: 必须先计算强匹配,再计算弱匹配。颠倒顺序会导致错误。因为强匹配是更“严格”的匹配,它优先消耗数字。
数据结构选择:数组还是哈希表?
对于 UVA 340 这道题,由于数字的范围通常很小(0-9),使用普通的整型数组作为频率数组是最高效和最简单的选择。
| 特性 | 整型数组 (e.g., int counts[10]) | 哈希表 (e.g., std::map) |
|---|---|---|
| 适用场景 | 键(数字)范围小且连续 | 键范围大或不连续 |
| 性能 | 访问速度快 (O(1)) | 访问速度相对慢 (O(logN) 或 O(1) 平均) |
| 内存 | 固定大小,可能略有浪费 (如果数字范围很小) | 动态分配,按需使用 |
| 实现难度 | 简单直观 | 稍复杂一点点 |
| UVA 340 推荐 | 强烈推荐 | 可用,但不必要,且可能稍慢 |
考虑到题目特性,使用两个 int[10] 的数组来存储 0-9 数字的频率,以及两个 bool[N] 的数组来标记秘密序列和猜测序列中哪些位置的数字已经被强匹配使用,是最佳实践。
代码实现思路概览
虽然不直接给出代码,但你可以按照以下逻辑来构建你的程序:
// 伪代码示例function solve_uva340():
while true:
read N // 读取序列长度
if N == 0:
break
read secret_code // 读取秘密序列
while true:
read guess_code // 读取猜测序列
if guess_code 中的所有数字都是 0: // 结束当前秘密序列的猜测
break
strong_matches = 0
weak_matches = 0
// 用于标记秘密序列和猜测序列中数字是否已被使用
bool secret_used[N] = {false}
bool guess_used[N] = {false}
// --- 第一步:计算强匹配 ---
for i from 0 to N-1:
if secret_code[i] == guess_code[i]:
strong_matches++
secret_used[i] = true
guess_used[i] = true
// --- 第二步:计算弱匹配 ---
// 频率数组,用于统计未使用的数字
int secret_counts[10] = {0}
int guess_counts[10] = {0}
// 填充秘密序列的频率数组
for i from 0 to N-1:
if not secret_used[i]:
secret_counts[secret_code[i]]++
// 填充猜测序列的频率数组
for i from 0 to N-1:
if not guess_used[i]:
guess_counts[guess_code[i]]++
// 计算弱匹配
for digit from 1 to 9: // 假设数字范围是 1-9
weak_matches += min(secret_counts[digit], guess_counts[digit])
print "强匹配: " + strong_matches + ", 弱匹配: " + weak_matches你可能想知道的
Q: 为什么不能先算白棋子(弱匹配)再算黑棋子(强匹配)?
A: 如果你先算弱匹配,可能会把那些既能作为强匹配也能作为弱匹配的数字都算成弱匹配,导致强匹配数量被低估,且总匹配数错误。强匹配是更严格、优先级更高的匹配,它要求值和位置都相同,应该优先处理。一旦一个数字形成了强匹配,它就不能再被看作弱匹配了。
Q: 如果有多个重复数字怎么办?比如秘密是 112,猜测是 121。
A: 这正是我们分步计数法的优势。
- 强匹配:
s[0](1) 和g[0](1) 相同。strong_matches = 1。标记s[0]和g[0]。- 此时,秘密剩余
_12,猜测剩余_21。
- 弱匹配:
- 秘密中未使用的数字频率:
cnt[1]=1(来自s[1]),cnt[2]=1(来自s[2])。 - 猜测中未使用的数字频率:
cnt[2]=1(来自g[1]),cnt[1]=1(来自g[2])。 - 计算
min(secret_counts[1], guess_counts[1]) = min(1,1) = 1。 - 计算
min(secret_counts[2], guess_counts[2]) = min(1,1) = 1。 weak_matches = 1 + 1 = 2。
最终结果是 1 个强匹配,2 个弱匹配。这种方法能够正确处理重复数字。
Q: 有没有更快的算法?
A: 对于 UVA 340 这种数字范围小(1-9)且序列长度有限(通常不超过 1000)的问题,我们上面介绍的这种 O(N) 的分步计数加频率数组的方法已经非常高效了。它只需要常数次遍历序列,并且频率数组的操作也是常数时间。在实际应用中,很难找到比这更快的算法,因为它已经接近理论最优了。
掌握了这些,你就可以轻松应对 UVA 340 (Master-Mind Hints) 这道题了。
一下,解决 UVA 340 的关键在于理解 Mastermind 游戏规则,特别是黑白棋子的含义,然后采用分步计数法,先计算强匹配并标记已用数字,再对剩余未用数字使用频率数组计算弱匹配,这是处理重复数字和避免逻辑错误的最佳实践,希望对你有用。
上一篇:uv uvc灯珠(真的能杀菌吗)