题解:洛谷 P3864 命名那个数字

【题目来源】

洛谷:P3864 [USACO1.2] 命名那个数字 Name That Number

【题目描述】

在威斯康辛州牛守志大农场经营者之中,都习惯于请会计部门用连续数字给母牛打上烙印。但是,母牛本身并没感到这个系统的便利,它们更喜欢用它们喜欢的名字来呼叫它们的同伴,而不是用像这个的语句:“C'mon, #4364, 相处愉快。”请写一个程序来帮助可怜的牧牛工将一只母牛的烙印编号翻译成一个可能的名字。因为母牛们现在都有手机了,可以按照下表来转换数字为字母:

数字 对应字母
\(2\) A,B,C
\(3\) D,E,F
\(4\) G,H,I
\(5\) J,K,L
\(6\) M,N,O
\(7\) P,R,S
\(8\) T,U,V
\(9\) W,X,Y

请注意没有字符 Q 和字符 Z

牛群们可接受的名字都被放在这样一个叫作 "dict.txt" 的文件中,它包含一连串的少于 \(5000\) 个(准确地说是 \(4617\) 个)可被接受的牛的名字。(所有的名字都是大写的且已按字典序排列) 请读入母牛的编号并返回那些能从编号翻译出来并且在字典中的名字。举例来说,编号 \(4734\) 能产生的 \(81\) 个名字中只有一个 "GREG" 是有效的(在字典中)。

写一个程序来对给出的编号打印出所有的有效名字,如果没有则输出 NONE

【输入】

第一行一行包含一个编号,长度 \(1\)\(12\)

接下来若干行,每行一个字符串表示可以被接受的名字。

【输出】

以字典顺序输出一个有效名字的不重复列表,一行一个名字。如果没有有效名字,输出 NONE

【输入样例】

4734
NMSL
GREG
LSDC
....(太多了不写了)

【输出样例】

GREG

【核心思想】

  1. 问题分析:给定一个数字编号(长度 \(1 \sim 12\)),每个数字对应 \(3\) 个字母(如 \(2 \to A,B,C\))。要求找出所有由该编号翻译出的字母组合中,存在于给定字典(\(4617\) 个名字)里的有效名字,按字典序输出。

  2. 算法选择

    • DFS 枚举所有组合:对每个数字位的 \(3\) 个字母进行深度优先搜索,生成所有可能的字符串
    • 哈希表快速查询:用 map<string, int> 存储字典,实现 \(O(1)\) 查询生成的字符串是否在字典中
    • 剪枝优化:生成完整字符串后才查字典,无法中途剪枝(因字典无序),但 \(3^{12} = 531441\) 可接受
  3. 关键步骤

    • 读入编号:字符串 \(s\),长度 \(n\),存入数组 \(a[1..n]\)
    • 建立字典:读入 \(4617\) 个名字,用 mp[name] = 1 标记存在
    • 定义字母映射表 c[10][3]\(2 \sim 9\) 各对应 \(3\) 个大写字母
    • DFS 函数 dfs(step, t)
      • step:当前处理到第几位(从 \(1\) 开始)
      • t:当前已构建的字符串前缀
      • step > n:生成完毕,若 mp[t] 存在则输出并标记 flag = true
      • 否则:取出当前位数字 num = a[step],枚举 \(i \in [0, 2]\),递归 dfs(step+1, t + c[num][i])
    • 输出判断:若 flag 仍为 false,输出 NONE
  4. 时间/空间复杂度

    • 时间复杂度:\(O(3^n)\),枚举所有字母组合,\(n \le 12\) 时最大 \(531441\)
    • 空间复杂度:\(O(4617)\),字典哈希表存储
  5. DFS 枚举 + 哈希查询的核心思想

    • 数字到字母的映射:每个数字固定对应 \(3\) 个字母,无歧义,直接查表
    • 全排列生成:DFS 按位展开形成一棵三叉树,叶子节点即为所有可能的 \(3^n\) 个字符串
    • 哈希判存在:用 map 将字典查询优化到 \(O(1)\),避免线性查找
    • 字典序自然保证:DFS 按字母表顺序(c[num][0], c[num][1], c[num][2])枚举,生成的字符串天然按字典序
    • 适用于密码翻译、组合枚举、字典匹配类问题

【算法标签】

普及 #DFS-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 15;        // 定义数组最大容量为15(编号最长12位)
int a[N], n;             // a[i]存储编号第i位的数字,n为编号长度
map<string, int> mp;     // mp存储字典中的所有名字,用于O(1)查询
// c[num][i]表示数字num对应的第i个字母(num从2到9,i从0到2)
char c[10][3] = {{}, {}, {'A', 'B', 'C'}, {'D', 'E', 'F'}, {'G', 'H', 'I'}, {'J', 'K', 'L'}, {'M', 'N', 'O'}, {'P', 'R', 'S'}, {'T', 'U', 'V'}, {'W', 'X', 'Y'}};
bool flag;               // flag标记是否找到至少一个有效名字

// 深度优先搜索:枚举编号每个数字对应的所有字母组合
// step:当前处理到第step位(从1开始)
// t:当前已构建的字符串前缀
void dfs(int step, string t)
{
    if (step > n)        // 如果已经处理完所有数字位
    {
        // cout << "t " << t << endl;  // 注释掉的调试输出
        if (mp[t])       // 如果当前字符串t在字典中存在
        {
            cout << t << endl;  // 输出该有效名字
            flag = true;        // 标记找到了有效名字
        }
        return;          // 递归返回
    }
    int num = a[step];   // 取出当前位对应的数字
    for (int i = 0; i < 3; i++)  // 枚举该数字对应的3个字母
    {
        dfs(step + 1, t + c[num][i]);  // 递归到下一层,将当前字母加入字符串
    }
}

int main()
{
    string s;
    cin >> s;            // 读入母牛的编号字符串
    n = s.size();        // 计算编号长度
    for (int i = 1; i <= n; i++)  // 将编号字符串转换为数字数组
        a[i] = s[i - 1] - '0';    // s[0]对应a[1],以此类推
    for (int i = 1; i <= 4617; i++)  // 读入4617个字典中的名字
    {
        string s;
        cin >> s;
        mp[s] = 1;       // 将名字加入字典映射
    }
    dfs(1, "");          // 从第1位开始DFS,初始字符串为空
    if (!flag)           // 如果没有找到任何有效名字
        cout << "NONE" << endl;  // 输出NONE
    return 0;
}

【运行结果】

4734
NMSL
GREG
LSDC
....
GREG
posted @ 2026-08-05 15:46  团爸讲算法  阅读(5)  评论(0)    收藏  举报