扩展 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;
}
posted @ 2020-12-21 22:05  zjjws  阅读(187)  评论(0)    收藏  举报