枚举 border 长度
设字符串长度为 。
若一个长度为 的前缀同时也是后缀,那么等价于
令
那么要比较的其实是两段等长字符串:
- 左边:;
- 右边:。
因此可以枚举位移 ,对应的 border 长度就是 。
对固定的 ,我们关心序列
和
在多少个位置上不同。
失配位置的分类
设这两段字符串中失配的位置为
由于一次询问只允许把某一个位置 改成 ,所以:
- 如果失配位置至少有 个,那么显然无解;
- 如果恰有 个失配位置,那么只能修改这对失配字符中的某一个;
- 如果恰有 个失配位置,那么只有当这两个失配“共用同一个原串位置”时,才可能通过修改这一个位置使它们同时消失。
下面分别讨论。
情况 1:没有失配
也就是原串 本身就有这个 border。
此时如果修改的位置完全不参与这组比较,那么这个 border 一定仍然成立。
什么时候会“不参与比较”呢?
当两段区间没有重叠时,中间可能会有一段空白区间:
- 前缀部分覆盖位置 ;
- 后缀部分覆盖位置 。
若 ,那么中间的
这些位置不属于前缀,也不属于后缀。修改这些位置不会影响这个 border。
情况 2:恰有一个失配
设唯一的失配位置为 ,即
若要修好它,只能改这两个位置之一。
但是还要注意:如果某个位置同时还参与了别的配对,那么改掉它可能会破坏别处。
因此只有下面两种情况可行:
- 修改左端点 ,要求它只出现在左串中,不再出现在右串中,也就是 ;同时还要满足
- 修改右端点 ,要求它只出现在右串中,不再出现在左串中,也就是 ;同时还要满足
情况 3:恰有两个失配
设最左边的失配位置为 ,最右边的失配位置为 ,即
如果这两组失配涉及的四个位置互不相同,那么修改一个字符至多修掉其中一组,显然无解。
唯一可能有解的情况是:
此时两组失配分别是
它们共用中间位置 。
这时只有修改中间位置 才可能同时消除两处失配,并且必须满足
如何在线性时间内判断失配个数
直接逐个比较所有 的话是 ,无法通过。
我们需要快速知道:
- 左边连续匹配了多长;
- 右边连续匹配了多长;
- 中间某一段是否完全相同。
用 Z 函数求最长前缀匹配
对原串 求一遍 Z 函数。
对于固定的 ,z[d] 就表示
的长度。
因为当前只关心长度为 的两段,所以真正有用的是
这就是两段字符串从左往右连续相同了多少位。
用反串的 Z 函数求最长后缀匹配
将字符串反转得到 ,对 再求一遍 Z 函数。
这样同样可以得到,对于固定的 ,两段字符串从右往左连续相同了多少位。
记:
lcp为左边连续匹配长度;lcs为右边连续匹配长度。
那么:
- 若
lcp = b,说明没有失配; - 若
lcp + lcs >= b-1,说明至多只有一个失配; - 否则,至少有两个失配。
用哈希判断是否恰好只有两个失配
当 lcp + lcs < b-1 时,我们已经知道最左失配位置和最右失配位置之间至少留出了一段中间区间。
设:
- 最左失配位置为 ;
- 最右失配位置为 。
如果中间这一段
完全匹配,那么失配就恰好只有两个;否则失配至少有三个。
这个“中间整段是否相等”的判断,可以用字符串哈希在 时间完成。
按 border 从大到小给答案
我们按
枚举,也就是按
从大到小枚举 border 长度。
因此对于每个位置 ,只要第一次发现它可以得到某个 border 长度 ,这个 就一定是答案。
为了避免一个位置被重复处理,可以维护一个并查集(或 next 数组跳点):
- 只把满足 的位置加入;
- 一旦某个位置答案确定,就把它从集合中删除;
- 之后再遇到区间赋值时,直接跳到下一个还没确定的位置。
这样每个位置最多被删除一次,总复杂度仍然是线性的。
原串本身不变的位置
如果某个位置满足
那么这次“替换”实际上没有发生任何变化,得到的字符串仍然是原串 。因此这些位置的答案,直接等于原串 的最大 border,这个值在枚举过程中顺便维护即可。
时间复杂度 ,空间复杂度 。