kmp与ac自动机

xj比赛做到一道字符串题,结果发现想打个字符串匹配都只会n^2了,又一次忘记了kmp(想必是当初学这玩意心理阴影面积太大了。。),这里再梳理一遍kmp和ac自动机(以便下次再忘了有地方看...)。

kmp:

用于处理对于字符串s,想知道它在另外某个串哪些位置出现的问题,先做预处理得到一个失配数组,这个数组第i位表示s的前i位后缀与前缀相同的最大长度。这个东西可以O(n)求。(当然这里注意不能整个后缀与前缀不能是当前整个串,不然失配你就在原地跳,RE起飞)

之后匹配,当匹配到s的第i位发现失配(或者完全匹配了)后,下一个有可能匹配到当前末尾的位数一定是当前这一位后缀与前缀相同的最大长度(手模一下即可),然后就搞定了。

复杂度分析:转自贴吧

这个可以用势能分析法来分析:
关于匹配指针的位置 cur
操作A:匹配时,cur++;
操作B:失配时,cur = next[cur ]; (根据不同实现有所出入)
这个 next[cur] <= cur - 1 是成立的。
根据势能分析(cur >= 0 恒成立),我们可以证明,操作A的执行次数一定比操作B要多,两个操作都是O(1)。
而操作A的执行次数是很容易分析最坏上界是 O(n)
那么 O(n) = T(A) >= T(B)
因此匹配时的时间复杂度 T(A + B) = O(n)

嗯。。很有道理(假装懂了)

代码:

#include
using namespace std;
const int N=1000010;
int n1,n2,nxt[N];char s1[N],s2[N];
void cal_fail()
{
    int k=-1;nxt[1]=0;
    for(int i=2;i<=n2;i++)
    {
        while(s2[k+1]!=s2[i]&&k)k=nxt[k];
        if(s2[k+1]==s2[i])++k;
        nxt[i]=k;
    }
}
void pp()
{
    int k=0;
    for(int i=1;i<=n1;i++)
    {
        while(s2[k+1]!=s1[i]&&k)k=nxt[k];
        if(s2[k+1]==s1[i])++k;
        if(k==n2)
        {
            printf("%d\n",i-n2+1);
            k=nxt[k];
        }
    }
}
int main()
{
    scanf("%s%s",s1+1,s2+1);
    n1=strlen(s1+1),n2=strlen(s2+1);
    cal_fail(),pp();
    for(int i=1;i<=n2;i++)
        printf("%d ",nxt[i]);
}

ac自动机:当初因为不会kmp所以这玩意也只会背代码,懂了kmp就好理解了。它可以说是kmp进化版,kmp用于求一个串在其他多个串哪些位置出现,ac自动机则可求多个串在其他多个串哪些位置出现。建法:先建一棵trie树,然后类似kmp的失配数组那样求出每个节点失配指针,就是字典树里能和当前这个位置的后缀匹配最长的前缀(和kmp类似,当然也不能是它本身),求法就是对于节点x,沿着父亲的失配指针跳,直到跳到某个节点,它有和x节点代表字符相同的字符的孩子(或者是一直跳到根也没有)。匹配也就类似kmp类比一下了。

代码:

#include
using namespace std;
char str[160][80],S[1000010];
struct Aho{
    struct gg{
        int nxt[26],fail,end;
    }node[12010];int size,cnt[160];
    queueq;
    void init()
    {
        size=1;
        for(int i=0;i<=12000;i++)
        memset(node[i].nxt,0,sizeof node[i].nxt),node[i].end=node[i].fail=0;
    }
    void insert(char *s,int id)
    {
        int n=strlen(s),pos=0,nw;
        for(int i=0;i

希望自己别再忘了。。

你可能感兴趣的:(刷题集)