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

板子:

  1 /*  这是一个板子 
  2 #include<iostream>
  3 #include<string>
  4 #include<cstring>
  5 #include<algorithm>
  6 #include<vector>
  7 #include<cmath>
  8 using namespace std;
  9 //算法主体
 10 int maxLcsplength(string str) {
 11     //空字符串直接返回0
 12     if (str.length() == 0) {
 13         return 0;
 14     }
 15     //记录下manacher字符串的长度,方便后面使用
 16     int len = (int)(str.length() * 2 + 1);
 17     //开辟动态数组chaArr记录manacher化的字符串
 18     //开辟动态数组pArr记录每个位置的回文半径
 19     char *chaArr = new char[len];
 20     int* pArr = new int[len];
 21     int index = 0;
 22     for (int i = 0; i < len;i++) {
 23         chaArr[i] = (i & 1) == 0 ? '#' : str[index++];
 24     }
 25     //到此完成对原字符串的manacher化
 26     //R是最右回文边界,C是R对应的最左回文中心,maxn是记录的最大回文半径
 27     int R = -1;
 28     int C = -1;
 29     int maxn = 0;
 30     //开始从左到右遍历
 31     for (int i = 0; i < len; i++) {
 32         //第一步直接取得可能的最短的回文半径,当i>R时,最短的回文半径是1,反之,最短的回文半径可能是i对应的i'的回文半径或者i到R的距离
 33         pArr[i] = R > i ? min(R - i, pArr[2 * C - i]) : 1;
 34         //取最小值后开始从边界暴力匹配,匹配失败就直接退出
 35         while (i + pArr[i]<len && i - pArr[i]>-1) {
 36             if (chaArr[i + pArr[i]] == chaArr[i - pArr[i]]) {
 37                 pArr[i]++;
 38             }
 39             else {
 40                 break;
 41             }
 42         }
 43         //观察此时R和C是否能够更新
 44         if (i + pArr[i] > R) {
 45             R = i + pArr[i];
 46             C = i;
 47         }
 48         //更新最大回文半径的值
 49         maxn = max(maxn, pArr[i]);
 50     }
 51     //记得清空动态数组哦
 52     delete[] chaArr;
 53     delete[] pArr;
 54     //这里解释一下为什么返回值是maxn-1,因为manacherstring的长度和原字符串不同,所以这里得到的最大回文半径其实是原字符串的最大回文子串长度加1,有兴趣的可以自己验证试试
 55     return maxn - 1;
 56 }
 57 int main()
 58 {
 59     string s1 = "";
 60     cout << maxLcsplength(s1) << endl;
 61     string s2 = "abbbca";
 62     cout << maxLcsplength(s2) << endl;
 63     return 0;
 64 }
 65 */
 66 
 67 
 68 /* 
 69 #include<stdio.h>
 70 #include<string.h>
 71 #include<iostream>
 72 #include<queue>
 73 #include<algorithm>
 74 using namespace std;
 75 const int maxn=5e5+10;
 76 char str[maxn*2],s[maxn];
 77 int len,Len[maxn*2],val[maxn],v[30];
 78 void init()
 79 {
 80     memset(str,0,sizeof(str));
 81     int k=0;
 82     str[k++]='$';
 83     for(int i=1;i<=len;++i)
 84     {
 85         str[k++]='#';
 86         str[k++]=s[i];
 87     }
 88     str[k++]='#';
 89     len=k;
 90 }
 91 void manacher()
 92 {
 93     Len[0]=0;
 94     int sum=0;
 95     int id,mx=0;
 96     for(int i=1;i<len;++i)
 97     {
 98         if(i<mx) Len[i]=min(mx-i,Len[2*id-i]);
 99         else Len[i]=1;
100         while(str[i-Len[i]]==str[i+Len[i]]) Len[i]++;
101         if(Len[i]+i>mx)
102         {
103             mx=Len[i]+i;
104             id=i;
105         }
106     }
107 }
108 int main()
109 {
110     int t;
111     scanf("%d",&t);
112     while(t--)
113     {
114         for(int i=0;i<26;++i)
115             scanf("%d",&v[i]);
116         scanf("%s",s+1);
117         len=strlen(s+1);  //3
118         
119         //printf("%d**
",len); //3
120         //init();
121         //manacher();
122         //printf("%d**
",len);  //8,这个是字符串处理完之后的长度 
123         //for(int i=0;i<=len;++i){
124         //    printf("%d %d
",i,Len[i]);
125         //} 
126         
127         
128         for(int i=1;i<=len;++i)
129         {
130             val[i]=val[i-1]+v[s[i]-'a'];
131         }
132         int n=len;
133         init();
134         manacher();
135         int ans=0;
136         for(int i = 1; i < n; i++){
137             int tmp = 0;
138             if(Len[i + 1] - 1 == i){
139                 if(i % 2){
140                     tmp += val[i / 2] * 2 + v[s[(i + 1) / 2] - 'a'];
141                 }
142                 else{
143                     tmp += val[i / 2] * 2;
144                 }
145             }
146             if(Len[i + n + 1] - 1 == n - i){
147                 if((n - i) % 2){
148                     tmp += (val[i + (n - i) / 2] - val[i]) * 2 + v[s[i + (n + 1- i) / 2] - 'a'];
149                 }
150                 else{
151                     tmp += (val[i + (n - i) / 2] - val[i]) * 2;
152                 }
153             }
154             ans = max(ans, tmp);
155         }
156         printf("%d
",ans);
157         
158     }
159     return 0;
160 } 
161 */
162 
163 
164 
165 /*
166 
167 #include <cstdio>
168 
169 #include <cmath>
170 
171 #include <cstring>
172 
173 #include <cstdlib>
174 
175 #include <iostream>
176 
177 #include <algorithm>
178 
179 #include <queue>
180 
181 #include <stack>
182 
183 using namespace std;
184 
185 char a[51000005];
186 
187 char s[102000005];
188 
189 int p[51000005];
190 
191 int maxp,p0,l;
192 
193 int ans=1;
194 
195 void manacher()
196 
197 {
198 
199     for(int i=1;i<l;i++)
200 
201     {
202 
203         if(i<maxp)
204 
205         {
206 
207             int j=(p0<<1)-i;
208 
209             p[i]=min(p[j],p[p0]+p0-i);
210 
211         }else
212 
213         {
214 
215             p[i]=1;
216 
217         }
218 
219         while(s[i-p[i]]==s[i+p[i]])
220 
221         {
222 
223             p[i]++;
224 
225         }
226 
227         if(i+p[i]>maxp)
228 
229         {
230 
231             p0=i;
232 
233             maxp=i+p[i];
234 
235         }
236 
237     }
238 
239 }
240 
241 int main()
242 
243 {
244 
245     scanf("%s",a+1);
246 
247     l=strlen(a+1);
248 
249     p[1]=1;
250 
251     s[0]=s[1]='#';
252 
253     for(int i=1;i<=l;i++)
254 
255     {
256 
257         s[2*i]=a[i];
258 
259         s[2*i+1]='#';
260 
261     }
262 
263     l=(l<<1)+2;
264 
265     s[l]='0';
266 
267     manacher();
268 
269     for(int i=1;i<=l;i++)
270 
271     {
272 
273         ans=max(ans,p[i]-1);
274 
275     }
276 
277     printf("%d
",ans);
278 
279     return 0;
280 
281 }
282 
283 */ 板子 
View Code
原文地址:https://www.cnblogs.com/kongbursi-2292702937/p/11626451.html