密码翻译 的题解


记住只在没有思路时使用题解,不要从它复制粘贴代码。请尊重题目和题解的作者。
在解题之前提交题解的代码会导致封禁。

作者: admin

概述

本题要求把一行按“每个字母替换为后继字母”加密得到的密文解密还原,即把每个字母替换为它在字母表中的前驱字母。核心解法是利用加密与解密互为逆运算这一性质,对每个字符做一次常数时间的映射,字母以外的所有字符原样输出。

分析
核心观察

加密规则把 26 个小写字母与 26 个大写字母各自映射到同类字母内部,是一个一一对应的置换,因此解密就是它的逆置换:密文字母往前移动一位即得明文,往前越过 a 或 A 时回绕到 z 或 Z。非字母字符在加密时本就不变,解密时同样原样保留。

思路

记密文的第 i 个字符为 s_i,解密后得到的第 i 个字符为 t_i。加密做的事是“后继”,即 a 变成 b、b 变成 c、……、y 变成 z,而 z 回绕成 a,大写字母同理。解密要把它倒过来,于是规则是“前驱”:

  • b 变成 a、c 变成 b、……、z 变成 y,特例 a 回绕成 z;
  • B 变成 A、C 变成 B、……、Z 变成 Y,特例 A 回绕成 Z;
  • 其余字符保持不变。

用公式概括就是

\displaystyle  t_i = s_i - 1 \quad (\text{当 } s_i \text{ 是未回绕的字母}), \qquad t_i = s_i \quad (\text{当 } s_i \text{ 不是字母})

回绕的两个特例单独处理:a 的前驱是 z,A 的前驱是 Z。

这里有两个容易出错的地方。第一,不能对所有字符统一做 ASCII 码减一:那样空格、标点等非字母字符会被改坏,而且 a 会变成反引号而不是 z,回绕必须显式处理。第二,回绕要分大小写,a 对应 z、A 对应 Z,把 a 变成 Z 或者把 A 变成 z 都是错的。

此外,题面背景中有一条更新说明:曾经有数据把 @ 错误地当成了 A,该问题已被修复。@ 不是字母,按规则原样输出,不能参与任何字母变换,这一点在解密实现里必须遵守。

具体示例

以样例 Ifmmp ! Ipx bsf zpv! 为例,逐段处理如下。

密文片段 处理 明文片段
Ifmmp I 是大写字母,前移为 H;f、m、m、p 依次前移为 e、l、l、o Hello
! 非字母,原样保留 !
连续两个空格 非字母,原样保留 连续两个空格
Ipx I、p、x 前移为 H、o、w How
bsf b、s、f 前移为 a、r、e are
zpv z 前移为 y,p、v 前移为 o、u you

拼接后得到 Hello ! How are you!,与样例输出完全一致。可以看到感叹号与两个连续空格都被原样保留,没有任何压缩或丢弃。

算法步骤
  1. 用 std::getline(Python 用 sys.stdin.readline)整行读入密文字符串。
  2. 从左到右枚举字符串中的每一个字符 c。
  3. 若 c 在 b 到 z 之间,把它替换为前一个字母。
  4. 否则若 c 等于 a,把它替换为 z。
  5. 否则若 c 在 B 到 Z 之间,把它替换为前一个字母。
  6. 否则若 c 等于 A,把它替换为 Z。
  7. 其余字符不做任何改动。
  8. 输出处理后的整行。
复杂度分析
  • 时间:O(n),其中 n 为字符串长度。每个字符只做常数次比较与一次赋值,长度上界为 10000。
  • 空间:O(n),需要存下整行文本。若改为逐字符读入并立即输出,可以做到 O(1),但整行保存的写法更简洁,10000 个字符的占用可以忽略。
实现注意事项
  • 必须整行读入。若用 cin >> 读 std::string,读入会在第一个空白字符处停下,行内空格与后半行全部丢失,样例第一行就会得到错误结果。
  • Python 读入后不要用 strip() 去空白,它会同时删掉行首与行末的空格;只应去掉行尾的换行符,例如 rstrip("\n")。
  • 连续空格必须原样保留,不能对空白做任何压缩、合并或归一化处理。
  • 大小写回绕分别进行:a 回绕到 z,A 回绕到 Z,两者不可混用。
  • @ 属于非字母字符,按题面背景的更新说明,它必须原样输出,不能当作 A 处理。
  • 不要对所有字符统一做 ASCII 码减一,否则非字母字符会被改坏,a 也会变成反引号而非 z。
  • 长度不超过 10000,无论用下标还是迭代器遍历都不存在越界或整型溢出风险。
  • 输入只有一行且以换行结尾;即使这一行为空,程序也应输出一个空行而不是出错。
源代码
#include <iostream>
#include <string>

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    std::string s;
    std::getline(std::cin, s);

    for (char &c : s) {
        if (c >= 'b' && c <= 'z') {
            c = c - 1;              // 小写字母前移一位
        } else if (c == 'a') {
            c = 'z';                // a 回绕到 z
        } else if (c >= 'B' && c <= 'Z') {
            c = c - 1;              // 大写字母前移一位
        } else if (c == 'A') {
            c = 'Z';                // A 回绕到 Z
        }
        // 其他非字母字符(空格、at 符号、标点等)原样保留
    }

    std::cout << s << '\n';
    return 0;
}
import sys


def main():
    s = sys.stdin.readline().rstrip("\n")

    res = []
    for ch in s:
        if "b" <= ch <= "z":
            res.append(chr(ord(ch) - 1))    # 小写字母前移一位
        elif ch == "a":
            res.append("z")                 # a 回绕到 z
        elif "B" <= ch <= "Z":
            res.append(chr(ord(ch) - 1))    # 大写字母前移一位
        elif ch == "A":
            res.append("Z")                 # A 回绕到 Z
        else:
            res.append(ch)                  # 其他非字母字符原样保留

    sys.stdout.write("".join(res) + "\n")


main()

评论

目前没有评论。