CF2196E:SA的应用

image-20260816202200641

首先贪心的思路是显然的,因此我们相当于在t串中不断求最长可错配lcp

法1:根号分治

我们考虑以 BB 分块

  • Case1:对于 dBd\le B 的情况。(即一次最多走 dd 步)

我们可以先预处理 ss 中所有小于等于 BB 的子串,并枚举它的哪个位置是空的。接下来把这个串挖掉这个空直接丢入hash中(相当于我们枚举的这一位直接赋值为0),这样的复杂度是 O(nB2)O(nB^2) 的。

然后我们现在处理 tt 串。我们可以枚举每一个被替换的位置 ii ,那么显然 ii 只有 BB 种可能。然后我们再去枚举 dd

但枚举 dd 的这个过程可以优化。假设对于一个 ii,如果 d+1d+1 可以,那么 dd 一定可以。因此我们可以维护当前最长 的 dmxd_{mx},在 ii 不断右移的过程中,去试试 dmx+1d_{mx}+1 是否可行(因为更短的就没必要了)。在这种情况下,iidd 构成了two-pointer,因此一次枚举的复杂度就是 O(B)O(B) 的。因此这一部分的复杂度是 O(mB)O(mB)

  • Case2:对于 d>Bd>B 的情况

在这种情况下,我们可以直接枚举 ss 串的开头 jj(显然要枚举 nn 次)。然后对于每个 s[j:n]s[j:n],我们让其与 t[l:m]t[l:m] 取一次LCP(这个可以SA+RMQ预处理),然后跳过LCP+1的位置,再取一次LCP,就可以求出它们两个的最长长度了。这样一次的复杂度是 O(n)O(n) 的。

而我们只有在Case1中发现 dd 至少可以取到 BB,我们才会进入这一轮的判断。因此只要进入了这一轮的判断,至少会走 mB\frac m B 步。因此这部分的复杂度是 O(nmB)O(\frac{nm}B) 的。


因此这种做法的总复杂度是 O(nB2+mB+nmB)O(nB^2+mB+\frac{nm}B),可以取 B=50B=50,可以通过easy version

法2:SA + 二分

首先我们通过预处理SA,可以对于当前的 t[l:m]t[l:m],在 O(1)O(1) 的时间内找到与 ss 所有后缀的LCP的最大值。

接下来有一个比较明显的性质:我们修改的这个位置,一定在小于等于这个最长LCP+1。

因为我们可以枚举这个修改的位置。枚举的总次数是 O(m)O(m) 的,因为这一轮我们至少会走LCP步。

枚举完修改位置,我们也可以直接枚举成替换成哪个字母。不妨假设在 psps 位置进行修改,修改完后待匹配的串是 tt'。(注意这个 tt' 是从 ll 位置开始的)

接下来我们想要寻找的是 tt'ss 所有后缀的最长LCP。

去找这个最长LCP,我们可以在 ss 串自己的后缀数组中,找到满足 sjtsks_j\le t'\le s_k,然后分别求 j,kj,ktt' 的LCP即可。

对于找 j,kj,k 的过程,我们可以采用二分的办法。以找 jj 为例,我们直接二分。然后求原串 tt 和当前 sjs_j 的LCP,不妨设为 qq

  • psqps\le q,我们可以直接比较出 tt'sjs_j 的大小关系(在 psps 位比较一下即可)
  • ps>q+1ps > q+1,我们也可以直接比较二者的大小关系(tt'tt 在这种情况下等价)
  • ps=q+1ps=q+1,我们可以再求一次 s[j+q+1:n]s[j+q+1:n]t[l+q+1:m]t[l+q+1:m] 的LCP,这也是 O(1)O(1) 的。

因此,我们可以在 log\log 的时间内求出 j,kj,k。同时,在上面的过程中,我们其实已经实现了求LCP的过程了。

总复杂度 O(26mlogn)O(26m\log n)