扩展 KMP(Z 函数)
能处理的问题形式
线性求:\(\operatorname {Lcp}([i:],[:n]),i\in [1,n]\)。
算法流程
回想一下 KMP 中我们记录的是什么?
\(nxt_i\) 表示 \([:i]\) 这个串中最长前缀后缀匹配长度。
而现在丢给我们的问题是:求 \([i:]\) 和 \([:n]\) 的最长前缀匹配,我们用 \(z_i\) 来表示这个值。
对于当前知道的右端点最靠右的匹配串 \([l:r]\)(与 \([1:r-l+1]\) 匹配),若有 \(i\le r\),那么 \([i:r]\) 与 \([i-l+1:r-l+1]\) 匹配。
我们如何利用当前已知的东西求出匹配长度?
\([i-l+1:r-l+1]\) 与 \([:n]\) 尝试匹配,可以看做是 \([i-l+1:]\) 与 \([:n]\) 尝试匹配后,其大小与 \(r-i+1\) 取一个 \(\min\) 值。
而这个 \([i-l+1:]\) 与 \([:n]\) 的匹配,则可以看做是相同类型的问题,就是 \(z_{i-l+1}\)。
那么这个转移可以看做是没有问题了,你已经完全理解了为什么这么转移是对的,剩下部分可能还有一些往后扩展的,直接暴力扫过去。
复杂度分析(也许算不上严谨的分析)
接下来就是考虑复杂度为什么是对的?
我们维护的 \(r\),实际上是 \([i:]\) 的最大可转移前缀的右端点,我们每次暴力扩展的时候,实际上就会将 \(r\) 更新。
那么这里的 \(l,r\) 就是双指针,这样的存在。
虽然说得比较模糊,但是确实就是 \(\operatorname O(n)\) 的。
模板代码
#include <stdio.h>
#include <string>
#include <string.h>
#include <iostream>
#define LL long long
using namespace std;
const int N=2e7+3;
inline int min(int x,int y){return x<y?x:y;}
inline LL min(LL x,LL y){return x<y?x:y;}
char a[N];
char b[N];
int n,m;
LL z[N];
LL ans;
inline void init()
{
LL l,r;
l=r=0;
z[1]=m;
ans=m+1;
for(LL i=2;i<=m;i++)
{
if(i<=r)z[i]=min(z[i-l+1],r-i+1);
for(;z[i]+i<=m&&b[z[i]+1]==b[z[i]+i];z[i]++);
if(i+z[i]-1>r)l=i,r=i+z[i]-1;
ans^=(z[i]+1)*i;
}
printf("%lld\n",ans);
return;
}
LL p[N];
inline void work()
{
LL l,r;
l=r=ans=0;
for(LL i=1;i<=n;i++)
{
if(i<=r)p[i]=min(z[i-l+1],r-i+1);
for(;p[i]+1<=m&&p[i]+i<=n&&b[p[i]+1]==a[p[i]+i];p[i]++);
if(i+p[i]-1>r)l=i,r=i+p[i]-1;
ans^=(p[i]+1)*i;
}
printf("%lld\n",ans);
return;
}
int main()
{
// freopen("P5410_2.in","r",stdin);
int i,j;
cin>>(a+1)>>(b+1);
n=strlen(a+1);m=strlen(b+1);
a[n+1]=5;
b[m+1]=10;
init();
work();
return 0;
}
$$\texttt{Dirty Deeds Done Dirt Cheap}$$