KMP && Manacher && 扩展KMP整理
阅读原文时间:2023年07月11日阅读:1

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
2 #include
3 #include
4 #include
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-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\n",ans);
60 }
61 return 0;
62 }

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

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

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

1 #include
2 #include
3 #include
4 #include
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\n",len);
47 }
48 return 0;
49 }

扩展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
2 #include
3 #include
4 #include
5 #include
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>t;cin>>s;
37 l1=strlen(t);
38 l2=strlen(s);
39 next_make();
40 match(0,0);
41 }
42 return 0;
43 }

例题:给一堆字符串,求最长公共字串。

找一个最短的串,暴力求出每一个后缀,和所有串匹配,找到每个extend里最大的,取总体最小,是一个答案,找到所有答案里长度最长的字典序最小的,就是答案。

1 #define ll long long
2 #define db double
3 #define ioss ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
4 using namespace std;
5 int n,cnt;
6 ll ext[220],nex[220];
7 string skr[4020];
8 string ans[4020];
9 void getNext(string &strp,ll nextt[])
10 {
11 ll pl=strp.size();
12 ll fir=0,las=0;
13 nextt[0]=pl;
14 for(ll i=1; i las)
23 {
24 las = i + nextt[i] - 1;
25 fir = i;
26 }
27 }
28 }
29 void exKMP(string &strp,string &strs,ll nextt[],ll extt[])
30 {
31 getNext(strp,nextt);
32 ll pl=strp.size(),sl=strs.size();
33 ll fir=0,las=-1,mnl=min(sl,pl);
34 while(laslas)
48 {
49 las=i+extt[i]-1;
50 fir=i;
51 }
52 }
53 }
54 int main()
55 {
56 while(scanf("%d",&n)==1&&n)
57 {
58 cnt=0;
59 int mnlen=300,mnlenx;
60 for(int i=1; i<=n; i++) 61 { 62 cin >> skr[i];
63 if (skr[i].size() < mnlen) 64 { 65 mnlen = skr[i].size(); 66 mnlenx = i; 67 } 68 } 69 for(int i=0; i0)
84 {
85 if(cnt==0||(mn==ans[1].size()))
86 {
87 ans[++cnt]=cur.substr(0,mn);
88 }
89 else if(mn>ans[1].size())
90 {
91 cnt=0;
92 ans[++cnt]=cur.substr(0,mn);
93 }
94 }
95 }
96 if(cnt)
97 {
98 sort(ans+1,ans+1+cnt);
99 cout<<ans[1]<<endl;
100 }
101 else cout<<"IDENTITY LOST"<<endl;
102 }
103 return 0;
104 }

Manacher算法:

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

例题:

HDU - 3613

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

题解:

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

代码:

1 #include
2 #include
3 #include
4 #include
5 #include
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;imx)
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\n",ans);
79 }
80 return 0;
81 }

板子:

1 /* 这是一个板子
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
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]-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
70 #include
71 #include
72 #include
73 #include
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;imx)
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**\n",len); //3 120 //init(); 121 //manacher(); 122 //printf("%d**\n",len); //8,这个是字符串处理完之后的长度 123 //for(int i=0;i<=len;++i){ 124 // printf("%d %d\n",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\n",ans); 157 158 } 159 return 0; 160 } 161 */ 162 163 164 165 /* 166 167 #include
168
169 #include
170
171 #include
172
173 #include
174
175 #include
176
177 #include
178
179 #include
180
181 #include
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;imaxp)
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\n",ans);
278
279 return 0;
280
281 }
282
283 */ 板子