洛谷 P1321 单词覆盖还原——从蜡板上的密信到数字水印

洛谷 P1321 单词覆盖还原------从蜡板上的密信到数字水印

📌 摘要

P1321 给一个被 boygirl 反复贴上、后贴覆盖先贴的字符串,问还原出各贴了几个。核心是从残留的字符碎片中推断原始单词数量------这本质上是一个"从被覆盖的信息中还原原始信息"的问题。这恰恰是密码学和信息隐藏领域的核心命题。本文从伪代码题解出发,延伸到信息隐藏的 2500 年历史------从公元前 480 年希腊人德马拉图斯在蜡板上藏密信,到今天 DCT 域数字水印和零宽度字符文本隐写。

题目链接P1321 单词覆盖还原

📚 目录

  • [📝 前言](#📝 前言)

  • [📜 信息隐藏的 2500 年](#📜 信息隐藏的 2500 年)

  • [🔍 题目在考什么](#🔍 题目在考什么)

  • [💡 解题思路](#💡 解题思路)

  • [📝 伪代码](#📝 伪代码)

  • [🎯 关键点](#🎯 关键点)

  • [⚠️ 注意事项](#⚠️ 注意事项)

  • [🌳 延伸:从覆盖还原到数字水印](#🌳 延伸:从覆盖还原到数字水印)

    • [🕯️ 蜡板藏信:信息隐藏的起源](#🕯️ 蜡板藏信:信息隐藏的起源)

    • [🔢 LSB 与 DCT:数字水印的两大流派](#🔢 LSB 与 DCT:数字水印的两大流派)

    • [✨ 零宽度字符:文本隐写术](#✨ 零宽度字符:文本隐写术)

    • [🔄 覆盖与还原:P1321 在信息隐藏中的位置](#🔄 覆盖与还原:P1321 在信息隐藏中的位置)

  • [📚 延伸阅读文献](#📚 延伸阅读文献)


📝 前言

这篇题解没有源代码,只有伪代码。

作为一名信奥教练,我不提倡复制粘贴。我见过太多学生搜到题解、复制、粘贴、提交、AC------代码跑通了,脑子没跑通。下次遇到变体题,还是不会。

伪代码剥掉了语言的壳,只留算法的骨架。你看不到 #include,看不到 cincout,看不到那些让你以为"我会了"的语法细节。你能看到的只有:这一步做什么、下一步做什么、为什么这么做。

如果你是路过的友友,已经在这道题上挣扎了很久------先去喝杯水,回来重新看看自己卡在哪一步。是没读懂题意?是思路方向偏了?还是代码有 bug 但逻辑其实对?大多数时候不是不会,是走偏了。偏了不可怕,可怕的是偏了之后直接放弃,去抄一份能 AC 的代码。抄完你以为你懂了,其实你只是搬了别人的结论。

除非你时间真的紧张------比赛临近、作业要交------那种情况先 AC 再说,能理解。但平时练习,给自己一点耐心。先自己想、自己写、自己调,跑不过了再来看伪代码:你的思路和这里差在哪一步。那一步,就是你真正学到的东西。


📜 信息隐藏的 2500 年

你在这道题里做的事情------从被覆盖的残缺信息中还原原始内容------在信息隐藏领域有一个正式的名字:隐写术(Steganography),意思是"被隐藏的书写"。

这个词最早出现在 1499 年德国修道士约翰内斯·特里特米乌斯(Johannes Trithemius)的著作 Steganographia 中。但隐写术本身的历史远比这个名字古老------至少可以追溯到 2500 年前(Evolution of Information-Hiding Technology)

公元前 480 年,波斯国王薛西斯一世集结大军准备入侵希腊。一个叫德马拉图斯(Demaratus)的希腊人住在波斯,得知了这个计划,需要把消息传回斯巴达。但波斯的关卡检查所有信件,密信怎么送出去?

德马拉图斯找来两块木制蜡板------当时常用的书写工具。他把蜡刮掉,在木板上刻下消息,再重新覆上蜡。蜡板看起来空空如也,关卡检查后放行。到了斯巴达,收信人刮掉蜡层,消息现形(Ancient Codebreaking --- University of New England)

这和 P1321 做的事情一模一样------把信息覆盖到载体上,只看表面看不到原始信息,需要"刮掉覆盖层"才能还原。

2500 年后的今天,蜡板换成了图像的 DCT 系数、文本的零宽度字符、音频的不可听频段。但核心命题没变:如何把信息嵌入载体、覆盖旧信息、且能从残缺中还原。


🔍 题目在考什么

一个最初全是句号 . 的字符串,被反复贴上 boygirl。后贴的覆盖先贴的,但每个单词至少有一个字符未被覆盖。给定最终字符串,问各贴了几个 boy、几个 girl

本质是:从覆盖后的残缺字符中,推断有多少个单词曾经被贴上去过。

每个单词至少有一个字符幸存------这就是还原的线索。你看到的不是完整的 boygirl,而是像 b.ybo..oyg.rl 这样的碎片。你需要从碎片中判断:这里原来贴过一个单词。

数据范围:3≤l≤255,字符仅包含 .bgilory


💡 解题思路

遍历字符串的每个位置,检查当前位置的字符是否可能是 boygirl 的某个位置。

boy 的检测 (3 个字符):对每个位置 i,检查 i、i+1、i+2 三个字符是否与 boy 的某个子串匹配。关键条件是"前一个字符不与前一个位置匹配"------防止同一个 boy 被重复计数。

girl 的检测(4 个字符):同理,但窗口是 4 个字符。

核心原则:一个字符如果可以归属到某个单词的某个位置,且不与已归属的字符重复,就算一个新单词。 题目保证"每个单词至少一个字符未被覆盖"------所以每个单词至少有一个"独占"的字符,可以被检测到。


📝 伪代码

text 复制代码
读取字符串 words
boy计数 = 0
girl计数 = 0
长度 = words.length()

对 i = 0 到 长度-1:

    // ===== 检测 boy =====
    // 位置 i 是否是 boy 的某个组成部分?
    // b 在开头
    如果 words[i] == 'b':
        boy计数++

    // o 在中间,且前面不是 b(防止同一个 boy 重复计)
    如果 words[i] == 'o' 且 words[i-1] != 'b':
        boy计数++

    // y 在末尾,且前面不是 o(防止同一个 boy 重复计)
    如果 words[i] == 'y' 且 words[i-1] != 'o':
        boy计数++

    // ===== 检测 girl =====
    // g 在开头
    如果 words[i] == 'g':
        girl计数++

    // i 在第二位,且前面不是 g
    如果 words[i] == 'i' 且 words[i-1] != 'g':
        girl计数++

    // r 在第三位,且前面不是 i
    如果 words[i] == 'r' 且 words[i-1] != 'i':
        girl计数++

    // l 在末尾,且前面不是 r
    如果 words[i] == 'l' 且 words[i-1] != 'r':
        girl计数++

输出 boy计数
输出 girl计数

伪代码简化说明

原始代码用了大量条件分支来处理各种覆盖情况(b+o+y、b+o+.、b+.+.、.+o+y 等)。伪代码将其简化为一个核心原则:每个字符独立判断是否属于某个单词的对应位置,用"前一个字符不是前驱"来防止重复计数。


🎯 关键点

"每个单词至少一个字符未被覆盖"的含义。 这是题目给出的还原保证。如果允许一个单词被完全覆盖,你根本看不到它存在过。题目保证至少一个字符幸存------这就是你能还原的"锚点"。在信息论中,这相当于"每个消息至少有 1 比特的信息未被覆盖"。

防重复计数的逻辑。 考虑 boy:当 i 指向 b,i+1 指向 o,i+2 指向 y。如果你在 b 和 o 和 y 各自位置都计一次,会得到 3 而不是 1。所以检测 o 时要检查 words[i-1] != 'b'------如果前一个字符已经是 b,说明这个 o 属于前一个 b 的 boy,不再重复计数。同理检测 y 时检查 words[i-1] != 'o'

用样例 ......boyogirlyy......girl....... 追踪:

位置 字符 boy 检测 girl 检测 boy 计数 girl 计数
6 b b→+1 --- 1 0
7 o 前面是 b→不加 --- 1 0
8 y 前面是 o→不加 --- 1 0
9 o 前面不是 b→+1 --- 2 0
10 g --- g→+1 2 1
11 i --- 前面是 g→不加 2 1
12 r --- 前面是 i→不加 2 1
13 l --- 前面是 r→不加 2 1
14 y 前面不是 o→+1 --- 3 1
15 y 前面不是 o→+1 --- 4 1
22 g --- g→+1 4 2
23 i --- 前面是 g→不加 4 2
24 r --- 前面是 i→不加 4 2
25 l --- 前面是 r→不加 4 2

输出:boy=4, girl=2。与样例一致。

为什么 i=14 的 y 会被计数? 因为 words[i-1] 是 l(不是 o),所以这是一个独立的 y------不属于任何已检测的 boy,但它本身是某个被覆盖的 boy 的幸存字符。这就是"从残缺中还原"。


⚠️ 注意事项

  • 数组越界 :代码中 words[i-1]words[i+1] 等访问在 i=0 或 i=length-1 时可能越界。实际代码中用大量条件分支规避了这个问题,但更安全的做法是先检查边界。

  • boy girl 共享字符 o 不共享------boy 的 o 和 girl 的 i 是不同字符。但 boy 的 y 和 girl 不会混淆,因为检测条件互不交叉。

  • 覆盖顺序的影响:后贴覆盖先贴。但题目保证每个单词至少一个字符幸存,所以不管什么覆盖顺序,总能检测到。

  • 字符集限定 :字符串只包含 .bgilory 七种字符,不需要处理其他字符。


🌳 延伸:从覆盖还原到数字水印

你在这道题里做的事情------把信息覆盖到载体上、然后从残缺中还原------在数字世界中有一整套对应技术:数字水印 (Digital Watermarking)和文本隐写术(Text Steganography)。

🕯️ 蜡板藏信:信息隐藏的起源

前面讲的德马拉图斯蜡板藏信的故事,是历史上最早的有记载的隐写术案例之一(Stéganographie --- Histoire)。公元前 480 年,他在蜡板木板上刻字后覆蜡,把波斯入侵的情报送到斯巴达。公元 1 世纪,老普林尼在《自然史》中记载了用大戟汁液做隐形墨水------干了透明,加热显形。

这些古代技术的共同点:把信息嵌入载体,使信息不可见,但能被接收方还原。 P1321 的覆盖还原,是同一个命题的简化版------只不过载体是字符串而不是蜡板,覆盖的是字符而不是蜡。


🔢 LSB 与 DCT:数字水印的两大流派

现代数字水印把"蜡板"换成了图像、音频、视频的数字数据。两大主流算法:

LSB(Least Significant Bit,最低有效位)替换法 :把秘密信息嵌入图像像素值的最低位。最低位的变化肉眼不可见,但携带了隐藏信息(信息隐藏技术实验教程)

text 复制代码
原始像素值: 10101100  ← 最低位是 0
嵌入比特 1: 10101101  ← 最低位改成 1,像素值只变了 1,肉眼不可见

LSB 简单但脆弱------压缩、裁剪都可能破坏水印。

DCT(Discrete Cosine Transform,离散余弦变换)域水印 :把图像从空间域变换到频域,在中频系数中嵌入水印(DCT Watermark Tool)。DCT 水印更鲁棒------即使图像被压缩、裁剪、模糊,水印仍然能被提取出来(Invisible Watermarking Tool)

LSB 替换法 DCT 域水印 P1321 覆盖还原
载体 图像像素最低位 图像 DCT 中频系数 字符串字符
嵌入方式 替换最低位 修改中频系数 覆盖字符
提取方式 读最低位 逆 DCT 后读系数 检测幸存字符
鲁棒性 弱(压缩即丢) 强(抗压缩/裁剪) 无(不抗任何修改)
可见性 不可见 不可见 部分可见(残缺字符)

P1321 是最原始的"覆盖-还原"模型------没有任何频率变换、没有最低位隐藏,就是粗暴的字符覆盖。但它揭示了信息隐藏的核心矛盾:嵌入的信息越多越容易被检测,嵌入的信息越少越容易在覆盖中丢失。


✨ 零宽度字符:文本隐写术

如果载体不是图像而是纯文本呢?零宽度字符 (Zero-Width Characters)是答案(用零宽度字符玩转文本隐写术)

Unicode 中有几个特殊的"零宽度"字符------它们存在于文本中,但渲染时不占任何宽度,肉眼完全不可见(StegoBidi --- Invisible Text Tool)

字符 Unicode 名称
U+200B Zero-Width Space 零宽空格
U+200C Zero-Width Non-Joiner 零宽非连接符
U+200D Zero-Width Joiner 零宽连接符
U+FEFF Zero-Width No-Break Space 零宽不换行空格

用这四个字符可以编码二进制信息:每个字符代表 2 个比特(00/01/10/11)。把秘密消息编码成零宽度字符序列,插入正常文本中------文本看起来完全没变,但隐藏了秘密数据(Python 实现文本隐形水印)

还有更高级的 Innamark 方法------用空白替换的方式在纯文本中隐藏信息,不需要特殊 Unicode 字符(Innamark: A Whitespace Replacement Information-Hiding Method)

P1321 和文本隐写的关系 :P1321 的字符串是"被覆盖后"的状态------你看到的是 boygirl 贴上去之后残留下来的字符。零宽度字符隐写则是"在正常文本中嵌入隐藏信息"------你看到的是正常文本,看不见的是隐藏的零宽度字符。两者方向相反但原理相通:都是在字符串中隐藏或还原信息。


🔄 覆盖与还原:P1321 在信息隐藏中的位置

把 P1321 放进信息隐藏的完整图景中:

层级 技术 做什么 P1321 对应
物理层 蜡板覆蜡 在木板上刻字后覆蜡 boy/girl 覆盖句号
字符层 P1321 覆盖还原 从残缺字符还原单词数量 ---
像素层 LSB 替换 在像素最低位藏信息 ---
频域层 DCT 水印 在中频系数藏信息 ---
文本层 零宽度字符 在不可见字符中藏信息 ---

P1321 处于"字符层"------最接近人类可读的层级。它没有做任何变换(不像 DCT 那样变换到频域),就是粗暴的字符覆盖。但正因为如此,它最适合用来理解"覆盖"和"还原"的本质:信息被嵌入载体后,总有一部分幸存;从幸存的部分,可以推断原始信息的全貌。

这就是信息隐藏的核心命题------从古代蜡板到现代水印,从 boy 覆盖到 DCT 系数,2500 年来人类一直在做同一件事。


📚 延伸阅读文献

论文
  1. J. Trithemius. Steganographia. 1499 (published 1606). ------ 隐写术一词的来源,历史上第一本系统讨论信息隐藏的著作。
  2. S. Katzenbeisser, F. Petitcolas. Information Hiding Techniques for Steganography and Digital Watermarking. Artech House, 2000. ------ 信息隐藏技术的经典教材。
  3. I. Cox, M. Miller, J. Bloom. Digital Watermarking and Steganography (2nd Edition). Morgan Kaufmann, 2007. ------ 数字水印的权威教材。
  4. C. Cabaj et al. Innamark: A Whitespace Replacement Information-Hiding Method . arXiv:2502.12710, 2025. (arXiv) ------ 2025 年最新的文本隐写方法。
  5. L. Wang, H. Liu. An efficient method for identifying and filling topographic depressions in digital elevation models. 2006. ------ 虽然是 GIS 算法,但"从残缺中还原"的思想与 P1321 相通。
在线资源
  1. 洛谷. P1321 单词覆盖还原 . https://www.luogu.com.cn/problem/P1321
  2. Stéganographie --- Histoire . https://www.steganographie.com/apprendre/histoire ------ 隐写术历史时间线,从蜡板到数字时代。
  3. DCT Watermark Tool (GitHub) . https://github.com/Mal-Suen/DCT_Watermark_Tool/ ------ C 语言 DCT 域水印工具,开源。
  4. StegoBidi --- Invisible Text Tool (GitHub) . https://github.com/kai9987kai/StegoBidi-Invisible-Text ------ 零宽度字符文本隐写工具。
  5. Evolution of Information-Hiding Technology . https://www.igi-global.com/chapter/evolution-computer-based-distance-learning/12582 ------ 信息隐藏技术的历史综述。
  6. BlindWaterMark 盲水印工具 (CSDN) . https://blog.csdn.net/gitblog_00509/article/details/152390330 ------ 盲水印原理介绍。
推荐教材
  • B. Schneier. Applied Cryptography (2nd Edition). Wiley, 1996. ------ 密码学经典,含隐写术章节。

  • W. Stallings. Cryptography and Network Security (7th Edition). Pearson, 2017. ------ 网络安全与密码学标准教材。

  • K. Rosen. Discrete Mathematics and Its Applications (8th Edition). McGraw-Hill, 2019. ------ 离散数学教材,含信息论与编码基础。


本文标签:#算法 #字符串 #信息隐藏 #数字水印 #隐写术 #洛谷题解 #信奥 #C++ #入门

本文首发于 CSDN,作者:HugoStudio_SWAN

相关推荐
春风解人意1 小时前
从零开始学习嵌入式P33----网络基础之HTTP
网络·学习·http
励志不掉头发的内向程序员1 小时前
【LibreCAD 2D架构】从鼠标点击到屏幕像素:LibreCAD绘图架构全链路解析之Action与命令系统
linux·开发语言·c++·qt·学习·系统架构
小道士写程序1 小时前
自然语言处理NLP - 第8章 词向量与神经网络:从手工特征到学习表示
神经网络·学习·自然语言处理
码匠许师傅2 小时前
【设计模式精讲】15.享元模式(Flyweight)
c++·设计模式·享元模式·uml
可爱系程序猿2 小时前
libcrypto-3-x64.dll 加载失败排查:OpenSSL 3 版本、PATH 顺序与 x64 依赖如何对应
linux·运维·服务器·程序人生·电脑
pingglala2 小时前
C++线程安全队列对比
开发语言·c++
刘孬孬沉迷学习2 小时前
AI音频领域完整调研:任务、算法、大模型研究全梳理
人工智能·算法·音视频
退休倒计时2 小时前
【每日五题】leetcode TypeScript
算法·leetcode·职场和发展·typescript
Demon--hx2 小时前
C++访问权限问题
开发语言·c++