Luogu P5410 【模板】扩展 KMP(Z 函数)

gate

$exkmp$,也称$z-algorithm$,

是用来求一个字符串的每个后缀与原串的$LCP$,即字符串中后缀=前缀的长度。

$z[i]$表示以第$i$位为开头的后缀与前缀相等的最大长度。

例如字符串$ababaa$,可求出它的$z$函数为$6 0 3 0 1 1$。

这个函数的求法有点类似于$manacher$。

首先,特别规定$z[1]$=字符串长度。

接下来从第二位枚举。设$i$为当前枚举到的字符串位数。

设$l$表示当前区间右端点最靠右的后缀的首位(左端点),即$i+z[i]$最大的$i$;

设$r$表示该区间的右端点,即$i+z[i]-1$。

对于当前的$i$,若有$i<=r$,即$i$被$l$的区间包括了,

即区间$(i,r)= $区间$(i-l+1,r-l+1)$。(都是闭区间)

所以,第$i$位与第$i-l+1$位相同,有$z[i] = z[i-l+1]$;

但这段区间也不能超过$r-l+1$,所以$z[i]<=r-i+1$。

综上所述,$z[i] = min(z[i-l+1],r-i+1)$。

后面的部分只要一位一位暴力枚举比较即可;

当$i>r$时,也用暴力枚举求出。

最后不要忘了更新$l$、$r$。

对于这道题的第二问($b$与$a$的每一个后缀的$LCP$长度),需在第一问的基础上求出。

只需要被比较的函数不变,用来比较的函数换成a即可。

设a的第$i$位开始的与b的LCP为$p[i]$

注意,在操作$p[i] = min(z[i-l+1],r-i+1)$时,不要把后面的$z[i-l+1]$写成$p[i-l+1]$(因为与自身相同的不是$a$字符串)。

注意这道题不要忘记$longlong$!(包括后面的$1ll$)

代码如下(吸氧过的)

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#define MogeKo qwq
using namespace std;

const int maxn = 2e7+10;

char a[maxn],b[maxn];
int len1,len2,z[maxn],p[maxn];
long long ans;

void read(char  

void get_z(char *s,int n) {
    z[1] = n;
    int l = 0,r = 0;
    for(int i = 2; i <= n; i++) {
        if(i <= r) z[i] = min(z[i-l+1],r-i+1);
        while(i+z[i] <= n && s[i+z[i]] == s[1+z[i]]) z[i]++;
        if(i+z[i]-1 > r) l = i, r = i+z[i]-1;
    }
}

void exkmp(char *s,char *t,int n) {
    int l = 0,r = 0;
    for(int i = 1; i <= n; i++) {
        if(i <= r) p[i] = min(z[i-l+1],r-i+1);
        while(i+p[i] <= n && s[i+p[i]] == t[1+p[i]]) p[i]++;
        if(i+p[i]-1 > r) l = i, r = i+p[i]-1;
    }
}

int main() {
    scanf("%s%s",a+1,b+1);
    len1 = strlen(a+1),len2 = strlen(b+1);
    get_z(b,len2);
    for(int i = 1; i <= len2; i++)
        ans ^= 1ll*i*(z[i]+1);
    printf("%lld\n",ans);
    ans = 0;
    exkmp(a,b,len1);
    for(int i = 1; i <= len1; i++)
        ans ^= 1ll*i*(p[i]+1);
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/mogeko/p/12485330.html
今日推荐