扩展KMP

扩展KMP

Z函数:对于一个长度为 n n n的字符串 S S S z [ i ] z[i] z[i]表示 S S S与后缀 S [ i , n ] S[i,n] S[i,n]的最长公共前缀LCP的长度。

image-20220618213731148

比如 S = a a a b a a a b c S=aaabaaabc S=aaabaaabc,其前缀有 a , a a , a a a , a a a b , a a a b a , a a a b a a , a a a b a a a , a a a b a a a b , a a a b a a a b c a,aa,aaa,aaab,aaaba,aaabaa,aaabaaa,aaabaaab,aaabaaabc a,aa,aaa,aaab,aaaba,aaabaa,aaabaaa,aaabaaab,aaabaaabc。设 S [ 2 , n ] = a a b a a a b c S[2,n]=aabaaabc S[2,n]=aabaaabc,其前缀有 a , a a , a a b , a a b a , a a b a a , a a b a a a , a a b a a a b , a a b a a a b c a,aa,aab,aaba,aabaa,aabaaa,aabaaab,aabaaabc a,aa,aab,aaba,aabaa,aabaaa,aabaaab,aabaaabc。发现 S S S S [ 2 , n ] S[2,n] S[2,n]的最长公共前缀为 a a aa aa,长度是 2 2 2,因此 z [ 2 ] = 2 z[2]=2 z[2]=2

Z-Box:它是字符串 S S S中的一个区间 [ l , r ] [l,r] [l,r],满足 S [ l , r ] S[l,r] S[l,r] S S S的前缀(不一定要求是最长),会随着 i i i的变化而移动,当在位置 i i i时, [ l , r ] [l,r] [l,r]必须包含 i i i,并且 r r r要尽可能的大。

如下图所示:

image-20220618214557825

这里有三个Z-Box。我们来看当 i = 8 i=8 i=8时, S [ i ] = c S[i]=c S[i]=c,它应该属于第二个Z-Box,因为第二个Z-Box是 b a c b bacb bacb满足是 S S S的前缀, c c c在这个Z-Box中,并且 r r r尽可能的大。当 i = 9 i=9 i=9时, S [ i ] = b S[i]=b S[i]=b,它应该属于第三个Z-Box而不是第二个Z-Box,因为假设它属于第二个Z-Box,那么虽然盒子长度大,但是它的 r = 9 r=9 r=9。而如果它属于第三个Z-Box,那么虽然盒子长度小,但是它的 r = 10 r=10 r=10尽可能的大。

我们可以在知道 i − 1 i-1 i1位置Z-Box的情况下,求出 z [ i ] z[i] z[i] i i i位置的Z-Box。

假设我们知道某个 [ l , r ] [l,r] [l,r],那么我们可以对称找到其在 S S S中的对应位置。设区间 [ l , r ] [l,r] [l,r]的长度为 l e n = r − l + 1 len=r-l+1 len=rl+1,对应点 l l l,其对称位置是起点 1 1 1。对于点 r r r,其对称位置是 1 + l e n − 1 = 1 + ( r − l + 1 ) − 1 = r − l + 1 1+len-1=1+(r-l+1)-1=r-l+1 1+len1=1+(rl+1)1=rl+1。设区间 [ l , i ] [l,i] [l,i]的长度为 L e n = i − l + 1 Len=i-l+1 Len=il+1,对于点 i i i,其对称位置是 1 + L e n − 1 = 1 + ( i − l + 1 ) − 1 = i − l + 1 1+Len-1=1+(i-l+1)-1=i-l+1 1+Len1=1+(il+1)1=il+1。于是我们可以得到这六个点。

算法流程:

计算完前 i − 1 i-1 i1个Z函数,维护盒子 [ l , r ] [l,r] [l,r],则 S [ l , r ] = S [ 1 , r − l + 1 ] S[l,r]=S[1,r-l+1] S[l,r]=S[1,rl+1]

  1. 如果 i ≤ r i\leq r ir(在盒内),则有 S [ i , r ] = S [ i − l + 1 , r − l + 1 ] S[i,r]=S[i-l+1,r-l+1] S[i,r]=S[il+1,rl+1]

    (1)若 z [ i − l + 1 ] < r − i + 1 z[i-l+1]<r-i+1 z[il+1]<ri+1,则 z [ i ] = z [ i − l + 1 ] z[i]=z[i-l+1] z[i]=z[il+1]

    (2)若 z [ i − l + 1 ] ≤ r − i + 1 z[i-l+1]\leq r-i+1 z[il+1]ri+1,则 z [ i ] = r − i + 1 z[i]=r-i+1 z[i]=ri+1,从 r + 1 r+1 r+1往后开始暴力枚举。

    image-20220618223220588

    对于在盒内的,即当 i ≤ r i\leq r ir时,我们可以取 z [ i ] = m i n ( z [ i − l + 1 ] , r − i + 1 ) z[i]=min(z[i-l+1],r-i+1) z[i]=min(z[il+1],ri+1)

  2. 如果 i > r i>r i>r(在盒外),则从 i i i开始暴力枚举。

    image-20220618223514915

  3. 求出 z [ i ] z[i] z[i]后,如果 i + z [ i ] − 1 > r i+z[i]-1>r i+z[i]1>r,则更新 l = i , r = i + z [ i ] − 1 l=i,r=i+z[i]-1 l=i,r=i+z[i]1

    如果找到了尽可能右的盒子,那么我们就要更改这个新盒子的左端点 l l l和右端点 r r r

代码:

void get_z(char *s, int n)
{
    z[1] = n;
    for (int i = 2, l, r = 0; i <= n; i ++ )
    {
        // 如果在盒内   整合第一种情况中的(1)和(2)
        if (i <= r) z[i] = min(z[i - l + 1], r - i + 1);
        // 无论i是在盒内还是在盒外,我们都先进行预判
        while (i + z[i] <= n && 1 + 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;
        }
    }
}

内层while循环的总次数决定了总的执行次数。对于while循环,如果条件成立则z++,即盒子的右端点 r r r就会后移若干位。而 r ≤ n r\leq n rn,如 r r r右移总步数不会超过 n n n。所以时间复杂度是 O ( n ) O(n) O(n)


题目描述

P5410


算法思路

image-20220618225019363


算法实现

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e7 + 10;
typedef long long LL;
// a是文本串  b是模式串
char a[N], b[N];
int z[N], p[N];
// n是模式串s的长度 m是文本串t的长度
int n, m;

// 求出模式串s的Z函数
void Z(char *s, int n)
{
    z[1] = n;
    for (int i = 2, l = 0, r = 0; i <= n; i ++ )
    {
        if (i <= r) z[i] = min(z[i - l + 1], r - i + 1);
        while (1 + z[i] <= n && i + z[i] <= n && s[1 + z[i]] == s[i + z[i]])    z[i] ++ ;
        if (i + z[i] - 1 > r)
        {
            l = i;
            r = i + z[i] - 1;
        }
    }
}

// s是模式串,n是模式串s的长度
// t是文本串,m是文本串t的长度
void exKMP(char *s, int n, char *t, int m)
{
    Z(s, n);    // 求出模式串s的Z函数
    for (int i = 1, l = 0, r = 0; i <= m; i ++ )
    {
        if (i <= r) p[i] = min(z[i - l + 1], r - i + 1);
        while (1 + p[i] <= n && i + p[i] <= m && s[1 + p[i]] == t[i + 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);
    m = strlen(a + 1), n = strlen(b + 1);       // m是文本串    n是模式串
    exKMP(b, n, a, m);
    LL ans = 0;
    for (int i = 1; i <= n; i ++ )  ans ^= 1ll * i * (z[i] + 1);
    printf("%lld\n", ans);
    ans = 0;
    for (int i = 1; i <= m; i ++ )  ans ^= 1ll * i * (p[i] + 1);
    printf("%lld\n", ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值