题解:洛谷 P1603 斯诺登的密码

【题目来源】

洛谷:P1603 斯诺登的密码 - 洛谷

【题目描述】

\(2013\)\(X\)\(X\) 日,俄罗斯办理了斯诺登的护照,于是他混迹于一架开往委内瑞拉的飞机。但是,这件事情太不周密了,因为 FBI 的间谍早已获悉他的具体位置——但这不是最重要的——最重要的是如果要去委内瑞拉,那么就要经过古巴,而经过古巴的路在美国的掌控之中。

丧心病狂的奥巴马迫降斯诺登的飞机,搜查时却发现,斯诺登杳无踪迹。但是,在据说是斯诺登的座位上,发现了一张纸条。纸条由纯英文构成:Obama is a two five zero.(以 . 结束输出,只有 \(6\) 个单词 + 一个句号,句子开头如没有大写亦为合法)这句话虽然有点无厘头,但是警官陈珺骛发现这是一条极其重要的线索。他在斯诺登截获的一台笔记本中找到了一个 C++ 程序,输入这条句子后立马给出了相对应的密码。陈珺鹜高兴得晕了过去,身为警官的你把字条和程序带上了飞机,准备飞往曼哈顿国际机场,但是在飞机上检查的时候发现——程序被粉碎了!飞机抵达华盛顿只剩 \(5\) 分钟,你必须在这 \(5\) 分钟内编写(杜撰)一个程序,免受上司的 \(10000000000 \ mod\  10\) 大板。破译密码的步骤如下:

(1)找出句子中所有用英文表示的数字(\(\le 20\)),列举在下:

正规:one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty

非正规:a both another first second third。为避免造成歧义,another 算作 \(1\) 处理。

(2)将这些数字平方后对 \(100\) 取模,如 \(00,05,11,19,86,99\)

(3)把这些两位数按数位排成一行,组成一个新数,如果开头为 \(0\),就去 \(0\)

(4)找出所有排列方法中最小的一个数,即为密码。

【输入】

一个含有 \(6\) 个单词的句子。

【输出】

一个整型变量(密码)。如果没有符合要求的数字出现,则输出 \(0\)

【输入样例】

Black Obama is two five zero .

【输出样例】

425

【核心思想】

  1. 问题分析:给定一个包含 6 个单词的英文句子,需要从中识别出所有表示数字的单词(包括正规数字 one~twenty 和特殊词 a、both、another、first、second、third),将其转换为对应的 1~20 的数值。然后对每个数值平方后取模 100,得到若干两位数(如 00、05、11 等)。将这些两位数按任意顺序排列拼接成一个新数,要求找出所有排列中最小的数,若没有数字则输出 0。本质是字符串匹配 + 全排列/排序 + 格式化拼接问题。

  2. 算法选择

    • 映射 + 排序:将每个单词映射到对应的数字,计算平方取模后存入数组。为了得到最小的拼接数,只需将数组升序排序,然后依次拼接即可(因为数值越小的两位数放在前面,形成的数最小)。需要注意前导零的处理(如 00 应跳过,05 应输出为 5 但在中间需补零)。
    • 复杂度:单词数量固定为 6,排序 \(O(1)\),非常高效。
  3. 关键步骤

    • 读入:循环读取 6 个单词(句子末尾的句点 . 会被作为单词读入,需注意匹配时排除)。
    • 映射查找:建立数字单词对照表 dc[26] 和对应数值 dn[26](包含正规和非正规单词)。对每个单词,遍历对照表查找匹配,若找到则计算 x = (dn[i] * dn[i]) % 100,存入数组 a
    • 排序:对数组 a 升序排序。
    • 输出
      • 若数组为空,输出 0
      • 否则,找到第一个非零数字(跳过前导 0),先输出该数字(不带前导零),然后后续数字以两位格式输出(如 printf("%02d", a[i]))。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(1)\),因为固定 6 个单词,对照表大小 26。
    • 空间复杂度:\(O(1)\)
  5. 字符串映射与最小拼接

    • 数字识别:句子中可能包含句点 .,但单词表中无此字符,因此不会被匹配。
    • 平方取模(1~20)^2 % 100 得到的是 0~99 的两位数(如 12=01,202=400→00)。
    • 最小排列:对于给定的一组两位数,升序排列后拼接得到的结果最小(这是字典序最小原则,因为所有数位长度相同)。
    • 前导零处理:拼接后开头若为 0 则去掉,但中间的 0 必须保留以保持数位。
    • 适用场景:这类“单词转数字 + 排列求最小”问题,是字符串处理与简单算法的结合。

【算法标签】

普及- #字符串入门

【代码详解】

#include <bits/stdc++.h>  // 包含标准库头文件(万能头文件)
using namespace std;      // 使用标准命名空间

int main()
{
    int a[10], k = 0;     // 定义数组a存储处理后的数字,k为计数器
    // 数字单词对照表
    string dc[] = {"one","two","three","four","five","six","seven","eight","nine","ten",
                   "eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen",
                   "eighteen","nineteen","twenty","a","both","another","first","second","third"};
    // 对应数字值
    int dn[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,1,2,1,1,2,3};
    string s;              // 定义字符串:存储输入的单词

    // 处理6个输入单词
    for (int j = 1; j <= 6; j++)
    {
        cin >> s;         // 输入单词
        // 在单词表中查找匹配
        for (int i = 0; i < 26; i++)
        {
            if (s == dc[i])  // 如果找到匹配的数字单词
            {
                int x = dn[i];  // 获取对应数字
                x = x * x % 100;  // 数字平方后取最后两位
                a[k++] = x;     // 存储到数组a中
            }
        }
    }

    // 如果没有找到任何数字单词,输出0并结束
    if (k == 0)
    {
        cout << 0;
        return 0;
    }

    sort(a, a + k);        // 对数组a中的数字进行升序排序

    // 跳过前导零(如果有)
    int st = 0;
    while (a[st] == 0 && st < k)
    {
        st++;
    }

    // 输出结果(第一个数字不带前导零,后面的数字保持两位数格式)
    printf("%d", a[st]);   // 输出第一个非零数字
    for (int i = st + 1; i < k; i++)
    {
        printf("%02d", a[i]);  // 输出后续数字(保持两位数)
    }

    return 0;              // 程序正常结束
}

【运行结果】

Black Obama is two five zero .
425
posted @ 2026-08-30 09:16  团爸讲算法  阅读(12)  评论(0)    收藏  举报