KMP自动机详解(CF808G)

啥??kmp还有自动机??

没错,我们经常听到ac自动机,后缀自动机之类的,kmp自动机我还真是第一次见。。。

前置知识:https://oi-wiki.org/string/kmp/ (kmp都不会还学啥kmp自动机)

我接触到这个知识点是由于我碰见了这么一个题目:Codeforces 808G

https://codeforces.com/contest/808/problem/G

中文题意在这:https://www.luogu.com.cn/problem/CF808G

最开始的想法是:设f[i][j]表示当前在S串的第i个位置,已经匹配了T串的前j个字符时(也就是对于任意的1<=k<=j ,有t[k]==s[i-j+k])T串出现次数的最大值

那么转移的时候,考虑在S串的第i+1个位置插入字符ch,然后就发现:这不就跟kmp里的两个字符串匹配时,模式串T利用next函数进行转移的情况一样嘛

然后就有以下的转移方程:

memset(f,-0x3f,sizeof(f));
f[0][0]=0;
for(int i=0;i<=n-1;i++){
    for(int j=0;j<=m;j++){
        char l='a',r='z';
        if(s[i+1]!='?') l=r=s[i+1];
        for(char ch=l;ch<=r;ch++){//枚举s[i+1]放什么字符
            int nj=j;
            while(nj>0&&(nj==m||t[nj+1]!=ch)) nj=nxt[nj];
            if(t[nj+1]==ch) nj++;//kmp的匹配过程
            f[(i+1)&1][nj]=max(f[(i+1)&1][nj],f[i&1][j]+(nj==m));
            //nj==m说明t出现了一次,这里用了滚动数组
        }
    }
    for(int j=0;j<=m;j++) f[i][j]=-inf;
}

愉快地交上去,发现:

T飞了啊!!

问题出在哪?

答案就是每次枚举j的时候,都是暴力的从j跳到nxt[j],如果遇到这样的情况:

bbbbbbbbb..(此处有100个b)

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...(此处有100000个a)

复杂度就被卡成o(n^m^2)的了。。

为了解决这个问题,就引入了一个新数组nxtc[i][c],表示模式串T的前i个位置已经全匹配上了,在第i+1个位置要匹配字符c的时候,经过kmp的next函数转移到的位置

如果已经得到nxtc数组的话,那么就直接可以o(1)转移j了

就像这样:

memset(f,-0x3f,sizeof(f));
f[0][0]=0;
for(int i=0;i<=n-1;i++){
    for(int j=0;j<=m;j++){
        char l='a',r='z';
        if(s[i+1]!='?') l=r=s[i+1];
        for(char ch=l;ch<=r;ch++){
            int nj=nxt[j][ch];//就是这里进行o(1)转移
            f[(i+1)&1][nj]=max(f[(i+1)&1][nj],f[i&1][j]+(nj==m));
        }
    }
    for(int j=0;j<=m;j++) f[i][j]=-inf;
}

那怎么得到nxtc数组呢??

暴力的写法是这样:

for(int o=0;o<=25;o++){
    for(int i=0;i<=m;i++){
        int ni=i;
        while(ni>0&&(ni==m||t[ni+1]!='a'+o)) ni=nxt[ni];
        if(t[ni+1]=='a'+o) ni++;
        nxtc[i][o]=ni;
    }
}

最坏情况依旧是o(m^2),一样会被T飞。。

而且这样写的话发现 i-1的nxtc数组完全没用上啊,我们可不可以用动态规划的思想利用已经求得的前i-1的nxtc去求当前i的nxtc

其实到这里已经很明显了:

假如已经知道了nxtc[nxt[i]][c],那么在i+1的位置匹配c时有两种情况:

1.一种是t[i+1]==c,那么nxtc[i][c]=i+1

2.另一种是t[i+1]!=c,那么nxtc[i][c]=nxtc[nxt[i]][c]

(这里AC自动机的fail指针的求解非常得像,可以说kmp自动机是AC自动机的基础)

i=0时初始值为:

如果c==t[1],则nxtc[0][c]=1

否则 nxtc[0][c]=0

for(int i=0;i<=m;i++){
    for(int o=0;o<=25;o++){
        if(t[i+1]=='a'+o) nxtc[i][o]=i+1;
        else{
            if(i==0) nxtc[i][o]=0;
            else nxtc[i][o]=nxtc[nxt[i]][o];
        }
    }
}

然后就愉快地AC了!

(oiwiki里原来有kmp自动机及其转移函数定义,是我孤陋寡闻了。。

https://oi-wiki.org/string/automaton/

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值