把 Vigenère 表看成模 26 加法:解密逐位做 (密文序号 − 密钥偏移 + 26) mod 26,密钥按 i mod |k| 循环取位,明文大小写跟随密文,扫描一遍 O(|C|)。
OJ: roj
题目 ID: 1402
难度:普及-
标签:字符串模拟数学python
创建: 2026-09-30 08:47
更新: 2026-10-07 13:50
前置题目
noi_openjudge ch0107-09密码翻译A 教的是字母序号平移并在字母表末尾回绕,B 的减法解密正是把这一步按位复用(mod 26 回绕),再叠加密钥循环取位与大小写跟随密文。noi_openjudge ch0107-10简单密码B 的 Vigenère 解法正是把 A 教的「字母转下标后减位移再 % 26 回绕转回字符」这一凯撒移位步骤逐位套用,只把固定位移 5 换成密钥位 k_{i mod n} 并补上密钥循环与大小写跟随luogu P1914小书童——凯撒密码B 把 A 教的字母折算 0..25、(x±n)%26 后回写字符这一步逐位套用,只是把固定位移 n 换成循环密钥位 k_{i mod |k|} 的偏移并补上大小写基准
题目描述
Vigenère 密码用密钥串
输入两行:第一行密钥 CompleteVictory、Yvqgpxaimmklongnzfwpvxmniytm,样例输出 Wherethereisawillthereisaway。
思路
- 加密是
,那张表只是把模 26 加法画了出来;解密移项得 ,每位 ,不必建表。 - 密钥第
位取 ,明文大小写跟随密文:算出序号后加回密文同款的 'a'或'A'。 - 易错点:相减可能为负(如
),模 26 应回绕成 ;C++ 的 %保留负号,需手动再+26。
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 08:51
# update_at: 2026-10-04 10:27
import sys
LOWER = ord('a') # 小写字母折算 0..25 的基准
UPPER = ord('A') # 大写字母基准:解出的明文大小写跟随密文字符
def plain_char(c: str, k: str) -> str:
"""解密一个字符:明文序号 = (密文序号 - 密钥位偏移) mod 26,大小写跟随密文。"""
shift = ord(k.lower()) - LOWER # 密钥位折算成 0..25 的加密偏移
base = LOWER if c.islower() else UPPER
return chr((ord(c) - base - shift) % 26 + base)
def solve() -> None:
data = iter(sys.stdin.buffer.read().decode().split()) # 按空白顺序切 token:先密钥后密文
key = next(data)
cipher = next(data)
klen = len(key)
print(''.join(plain_char(c, key[i % klen]) for i, c in enumerate(cipher)))
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-05 12:53
* update_at: 2026-10-05 12:53
*/
#include <cstdio>
#include <cstring>
#include <cctype>
using namespace std;
typedef long long ll;
const int MAXN = 1005;
char key_s[MAXN]; // 密钥串,长度不超过 100
char cipher_s[MAXN]; // 密文串,长度不超过 1000
char plain_s[MAXN]; // 解出的明文
int main() {
scanf("%s", key_s);
scanf("%s", cipher_s);
ll n = strlen(key_s);
ll m = strlen(cipher_s);
for (ll i = 0; i < m; i++) {
// 密钥位统一按小写折算成 0..25 的偏移量,运算忽略大小写
ll shift = tolower(key_s[i % n]) - 'a';
// 明文大小写跟随密文:基准字符取 'a' 或 'A'
char base = islower(cipher_s[i]) ? 'a' : 'A';
// 解密为模 26 减法;C++ 的 % 会保留负号,所以再加一次 26 回绕
ll idx = (cipher_s[i] - base - shift) % 26;
if (idx < 0) {
idx = idx + 26;
}
plain_s[i] = base + idx;
}
plain_s[m] = '\0';
printf("%s\n", plain_s);
return 0;
}
