KMP && Manacher && 扩展KMP整理

Posted kongbursi-2292702937

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP && Manacher && 扩展KMP整理相关的知识,希望对你有一定的参考价值。

KMP算法:

kmp示例代码:

void cal_next(char *str, int *next, int len)
{
    next[0] = -1;//next[0]初始化为-1,-1表示不存在相同的最大前缀和最大后缀
    int k = -1;//k初始化为-1
    for (int q = 1; q <= len-1; q++)
    {
        while (k > -1 && str[k + 1] != str[q])//如果下一个不同,那么k就变成next[k],注意next[k]是小于k的,无论k取任何值。
        {
            k = next[k];//往前回溯
        }
        if (str[k + 1] == str[q])//如果相同,k++
        {
            k = k + 1;
        }
        next[q] = k;//这个是把算的k的值(就是相同的最大前缀和最大后缀长)赋给next[q]
    }
}
int KMP(char *str, int slen, char *ptr, int plen)
{
    int *next = new int[plen];
    cal_next(ptr, next, plen);//计算next数组
    int k = -1;
    for (int i = 0; i < slen; i++)
    {
        while (k >-1&& ptr[k + 1] != str[i])//ptr和str不匹配,且k>-1(表示ptr和str有部分匹配)
            k = next[k];//往前回溯
        if (ptr[k + 1] == str[i])
            k = k + 1;
        if (k == plen-1)//说明k移动到ptr的最末端
        {
            //cout << "在位置" << i-plen+1<< endl;
            //k = -1;//重新初始化,寻找下一个
            //i = i - plen + 1;//i定位到该位置,外层for循环i++可以继续找下一个(这里默认存在两个匹配字符串可以部分重叠),感谢评论中同学指出错误。
            return i-plen+1;//返回相应的位置
        }
    }
    return -1;  
}
    char *str = "bacbababadababacambabacaddababacasdsd";
    char *ptr = "ababaca";
    int a = KMP(str, 36, ptr, 7);
    return 0;

 

kmp算法是用来找模式串是否在主串中出现,并返回第一次出现的位置。(模式串一般都比主串长度短,求的是模式串在主串中是否出现)

它有一个数组next[len](len是ptr字符串的长度),next[i]这里面放的是模式串的前i个字符的最长公共前后缀。(前缀不包括第i个字符)

时间复杂度:O(n+m)

 

算法过程:

你求的是模式串在主串中出现的位置,next[i]数组是模式串前i个字符的最长公共前后缀。
S1=abadfgag 
S2=abac
next[0]=-1
next[1]=-1
next[2]=1
next[3]=-1
第一次匹配从S1第0位开始,得到S1[0,2]==S2[0,2]
那么下一次匹配还要从S1第1位开始吗?
不需要。kmp优化的就是这里,因为next[2]=1
那么就有S2[0]=S2[2],又因为S1[0,2]==S2[0,2]
所以S1[0]==S1[2],那么我们这次就可以直接从S1第3位开始匹配

具体过程点这里

 

例题:传送门

题意:

t组输入,给你一个模式串ptr,让你找出来它在str中出现的次数

题解:

利用KMP算法,只需要在kmp原来的基础上稍微改一下就可以用来求次数

技术图片
 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<iostream>
 4 #include<algorithm>
 5 using namespace std;
 6 const int maxn=1000005;
 7 const int INF=0x3f3f3f3f;
 8 char str[maxn],ptr[maxn];
 9 int ans;
10 void get_next(int len,int *next)
11 {
12     next[0]=-1;
13     int k=-1;
14     for(int i=1;i<=len-1;++i)
15     {
16         while(k>-1 && ptr[k+1]!=ptr[i])
17             k=next[k];
18         if(ptr[k+1]==ptr[i])
19         {
20             k+=1;
21         }
22         next[i]=k;
23     }
24 }
25 void kmp(int slen,int plen)
26 {
27     int next[plen]; //这个next数组长度只能定义plen个长度,大的话会出错 
28     get_next(plen,next);
29     int k=-1;
30     for(int i=0;i<slen;++i)
31     {
32         while(k>-1 && ptr[k+1]!=str[i])
33         {
34             k=next[k];
35         }
36         if(ptr[k+1]==str[i])
37         {
38             k=k+1;
39         }
40         if(k==plen-1)
41         {
42             ans++;
43             k=next[k];
44             //i=i-plen+1+1;
45         }
46     }
47 }
48 int main()
49 {
50     int t;
51     scanf("%d",&t);
52     while(t--)
53     {
54         ans=0;
55         scanf("%s%s",ptr,str);
56         int slen=strlen(str);
57         int plen=strlen(ptr);
58         kmp(slen,plen);
59         printf("%d
",ans);
60     }
61     return 0;
62 }
View Code

 

有些题目利用next数组的特性来做,比如G - Seek the Name, Seek the Fame

这道题目需要找出来模式串中前后缀的所有相同长度,而不只是求最长

比如“alala”,它的最长相同前后缀是“ala”,但是还有“a”。等到把所有前后缀相同长度输出之后,再输出一下模式串长度就可以了。

技术图片
 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<iostream>
 4 #include<algorithm>
 5 using namespace std;
 6 const int maxn=400005;
 7 const int INF=0x3f3f3f3f;
 8 char ptr[maxn];
 9 int w[maxn];
10 void get_next(int len,int *next)
11 {
12     next[0]=-1;
13     int k=-1;
14     for(int i=1;i<=len-1;++i)
15     {
16         while(k>-1 && ptr[k+1]!=ptr[i])
17             k=next[k];
18         if(ptr[k+1]==ptr[i])
19         {
20             k+=1;
21         }
22         next[i]=k;
23     }
24 }
25 int main()
26 {
27     int n;
28     while(~scanf("%s",ptr))
29     {
30         int len=strlen(ptr);
31         int next[len];
32         get_next(len,next);
33         int g=0,k=next[len-1];
34         //w[g++]=next[len-1]+1;
35         while(k>=0)
36         {
37             w[g++]=k+1;
38             k=next[k];
39 
40         }
41         sort(w,w+g);
42         for(int i=0;i<g;++i)
43             if(w[i]!=w[i-1] && i!=0)
44             printf("%d ",w[i]);
45             else if(i==0) printf("%d ",w[i]);
46         printf("%d
",len);
47     }
48     return 0;
49 }
View Code

 

扩展kmp:

扩展KMP求的是对于原串S1的每一个后缀子串与模式串S2的最长公共前缀。它有一个next[]数组和一个extend[]数组。

next[i]表示为模式串S2中以i为起点的后缀字符串和模式串S2的最长公共前缀长度.

extend[i]表示为以字符串S1中以i为起点的后缀字符串和模式串S2的最长公共前缀长度.

 

复杂度:拓展kmp算法的总体复杂度为O(n+m)的。其中n为母串的长度,m为子串的长度。

 

算法过程:

S1=aaabaaaaaab
S2=aaaaab
第一步,我们先对原串S1和模式串S2进行逐一匹配,直到发生不配对的情况。我们可以看到,S1[0]=S2[0],S1[1]=S2[1],S1[2]=S2[2],S1[3] ≠S2[3],此时匹配失败,第一步结束,我们得到S1[0,2]=S2[0,2],即extend[0]=3;
Extend[0]的计算如第一步所示,那么extend[1]的计算是否也要从原串S1的1位置,模式串的0位置开始进行逐一匹配呢?扩展KMP优化的便是这个过程。从extend[0]=3的结果中,我们可以知道,S1[0,2]=S2[0,2],那么S1[1.2]=S2[1,2]。因为next[1]=4,所以S2[1,4]=S2[0,3],即S2[1,2]=S[0,1],可以得出S1[1,2]=S2[1,2]=S2[0,1],然后我们继续匹配,S1[3] ≠S2[3],匹配失败,extend[1]=2;
之后也这样
 

 

给出n组询问,每一组询问包含两个字符串t s,问s中是否包含t。(t中有’?’,’?’可以代替任何字符)。

技术图片
 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<string>
 5 #include<algorithm>
 6 using namespace std;
 7 int l1,l2,n;
 8 char s[100009],t[100009];
 9 int nxt[100009];
10 void next_make()
11 {
12     int t1=0,t2=-1;
13     nxt[0]=-1;
14     while(t1<l1)
15     {
16         if(t2==-1||t[t1]==t[t2]||t[t2]==?) nxt[++t1]=++t2;
17         else t2=nxt[t2];
18     }
19 }
20 void match(int t1,int t2)
21 {
22     while(t1<l1&&t2<l2)
23     {
24         if(t1==-1||t[t1]==s[t2]||t[t1]==?) t1++,t2++;
25         else t1=nxt[t1];
26     }
27     if(t1==l1) printf("God bless You!
");
28     else printf("Game Over!
");
29     return; 
30 }
31 int main()
32 {
33     scanf("%d",&n);
34     while(n--)
35     {
36         cin>>t;cin>>s;
37         l1=strlen(t);
38         l2=strlen(s);
39         next_make();
40         match(0,0);
41     }
42     return 0;
43 } 
View Code

 

Manacher算法:

当我们遇到字符串为“aaaaaaaaa”,之前的算法就会发生各个回文相互重叠的情况,会产生重复计算,然后就产生了一个问题,能否改进?答案是能,1975年,一个叫Manacher发明了Manacher Algorithm算法,俗称马拉车算法,其时间复杂为O(n)。

 

例题:

HDU - 3613

给你一个字符串(仅含字母),每个字母有一个价值,把字符串切断成两部分,若其中一部分为回文串,则其价值等于各字母价值和相加;否则为0。总价值为两部分价值相加,求最大价值。

题解:

先用manacher求出回文子串长度,再枚举切割点,若切后子串的中点是回文中心,且回文长度扩展到i点,则这个子串为回文,记录价值。最后输出最大价值。

代码:

技术图片
 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<iostream>
 4 #include<queue>
 5 #include<algorithm>
 6 using namespace std;
 7 const int maxn=5e5+10;
 8 char str[maxn*2],s[maxn];
 9 int len,Len[maxn*2],val[maxn],v[30];
10 void init()
11 {
12     memset(str,0,sizeof(str));
13     int k=0;
14     str[k++]=$;
15     for(int i=1;i<=len;++i)
16     {
17         str[k++]=#;
18         str[k++]=s[i];
19     }
20     str[k++]=#;
21     len=k;
22 }
23 void manacher()
24 {
25     Len[0]=0;
26     int sum=0;
27     int id,mx=0;
28     for(int i=1;i<len;++i)
29     {
30         if(i<mx) Len[i]=min(mx-i,Len[2*id-i]);
31         else Len[i]=1;
32         while(str[i-Len[i]]==str[i+Len[i]]) Len[i]++;
33         if(Len[i]+i>mx)
34         {
35             mx=Len[i]+i;
36             id=i;
37         }
38     }
39 }
40 int main()
41 {
42     int t;
43     scanf("%d",&t);
44     while(t--)
45     {
46         for(int i=0;i<26;++i)
47             scanf("%d",&v[i]);
48         scanf("%s",s+1);
49         len=strlen(s+1);
50         for(int i=1;i<=len;++i)
51         {
52             val[i]=val[i-1]+v[s[i]-a];
53         }
54         int n=len;
55         init();
56         manacher();
57         int ans=0;
58         for(int i = 1; i < n; i++){
59             int tmp = 0;
60             if(Len[i + 1] - 1 == i){
61                 if(i % 2){
62                     tmp += val[i / 2] * 2 + v[s[(i + 1) / 2] - a];
63                 }
64                 else{
65                     tmp += val[i / 2] * 2;
66                 }
67             }
68             if(Len[i + n + 1] - 1 == n - i){
69                 if((n - i) % 2){
70                     tmp += (val[i + (n - i) / 2] - val[i]) * 2 + v[s[i + (n + 1- i) / 2] - a];
71                 }
72                 else{
73                     tmp += (val[i + (n - i) / 2] - val[i]) * 2;
74                 }
75             }
76             ans = max(ans, tmp);
77         }
78         printf("%d
",ans);
79     }
80     return 0;
81 }
View Code

 

以上是关于KMP && Manacher && 扩展KMP整理的主要内容,如果未能解决你的问题,请参考以下文章

[kuangbin带你飞]专题十六 KMP & 扩展KMP & Manacher :G - Power Strings POJ - 2406(kmp简单循环节)

kuangbin专题十六 KMP&&扩展KMP HDU3613 Best Reward(前缀和+manacher or ekmp)

HDU 3068 &&HDU 3294 +最长回文串*3—— manacher/扩展KMP/DP

[kuangbin带你飞]专题十六 KMP & 扩展KMP & Manacher H - Seek the Name, Seek the Fame POJ - 2752(kmp的nex

HDU 3068 最长回文(Manacher)

HDU 4513 吉哥系列故事――完美队形II(Manacher)