KMP&扩展KMP&Manacher算法基础与习题(第二更)
2019-08-02 17:36
155 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43870697/article/details/98210490
KMP&扩展KMP&Manacher算法基础与习题(第一更)
KMP&扩展KMP&Manacher算法基础与习题(第三更)
目录
A:HDU-2594 Simpsons’ Hidden Talents
扩展KMP算法讲解(转自扩展KMP算法)
问题定义:给定两个字符串 S 和 T(长度分别为 n 和 m),下标从 0 开始,定义
extend[i]等于
S[i]...S[n-1]与 T 的最长相同前缀的长度,求出所有的
extend[i]。举个例子,看下表:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
S | a | a | a | a | a | b | b | b |
T | a | a | a | a | a | c | ||
extend[i] | 5 | 4 | 3 | 2 | 1 | 0 | 0 | 0 |
为什么说这是 KMP 算法的扩展呢?显然,如果在 S 的某个位置 i 有
extend[i]等于 m,则可知在 S 中找到了匹配串 T,并且匹配的首位置是 i。而且,扩展 KMP 算法可以找到 S 中所有 T 的匹配。接下来具体介绍下这个算法。
算法流程:
(1)
如上图,假设当前遍历到 S 串位置 i,即
extend[0]...extend[i - 1]这 i 个位置的值已经计算得到。设置两个变量,a 和 p。p 代表以 a 为起始位置的字符匹配成功的最右边界,也就是 "p = 最后一个匹配成功位置 + 1"。相较于字符串 T 得出,S[a...p) 等于 T[0...p-a)。
再定义一个辅助数组
int next[],其中
next[i]含义为:
T[i]...T[m - 1]与 T 的最长相同前缀长度,m 为串 T 的长度。举个例子:
i | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
T | a | a | a | a | a | c |
next[i] | 6 | 4 | 3 | 2 | 1 | 0 |
(2)
S[i]对应
T[i - a],如果
i + next[i - a] < p,如上图,三个椭圆长度相同,根据 next 数组的定义,此时
extend[i] = next[i - a]。
(3)
如果
i + next[i - a] == p呢?如上图,三个椭圆都是完全相同的,
S[p] != T[p - a]且
T[p - i] != T[p - a],但
S[p]有可能等于
T[p - i],所以我们可以直接从
S[p]与
T[p - i]开始往后匹配,加快了速度。
(4)
如果
i + next[i - a] > p呢?那说明
S[i...p)与
T[i-a...p-a)相同,注意到
S[p] != T[p - a]且
T[p - i] == T[p - a],也就是说
S[p] != T[p - i],所以就没有继续往下判断的必要了,我们可以直接将
extend[i]赋值为
p - i。
(5)最后,就是求解 next 数组。我们再来看下
next[i]与
extend[i]的定义:
- next[i]:
T[i]...T[m - 1]
与 T 的最长相同前缀长度; - extend[i]:
S[i]...S[n - 1]
与 T 的最长相同前缀长度。
恍然大悟,求解
next[i]的过程不就是 T 自己和自己的一个匹配过程嘛,下面直接看代码。
模板
[code]void Getnext(char *str) { int i=0,j,po,len=strlen(str); nex[0]=len; while(str[i]==str[i+1]&&i+1<len) i++; nex[1]=i; po=1; for(i=2;i<len;i++) { if(nex[i-po]+i<nex[po]+po) nex[i]=nex[i-po]; else { j=nex[po]+po-i; if(j<0)j=0; while(i+j<len&&str[i+j]==str[j]) j++; nex[i]=j; po=i; } } } void EXKMP(char *s1,char *s2,int ex[]) { int i=0,j,po,len=strlen(s1),l2=strlen(s2); Getnext(s2); while(s1[i]==s2[i]&&i<len&&i<l2) i++; ex[0]=i; po=0; for(i=1;i<len;i++) { if(nex[i-po]+i<ex[po]+po) ex[i]=nex[i-po]; else { j=ex[po]+po-i; if(j<0)j=0; while(i+j<len&&j<l2&&s1[i+j]==s2[j]) j++; ex[i]=j; po=i; } } }
例题
A:HDU-2594 Simpsons’ Hidden Talents:题目主要意思 就是让你找出前面一串字符的前缀和后面字符串的相同的后缀 ,并且打印这个字符串的长度 我的做法就是把两个字符串拼接起来,用KMp算法的NEXT数组可以求相同的前缀后缀,(next[len]即为后缀与前缀相等的长度)但是要注意,求出的长度不应该大于原来的最短字符串的长度,AC代码:
[code]#include <cstring> #include <iostream> #include <cstdio> #include <fstream> #include <algorithm> using namespace std; const int maxn=1e6+7; const int INF=0x3f3f3f3f; #define M 50015 int next1[maxn]; char str[maxn],mo1[maxn],mo2[maxn]; int ans; void getnext() { int i=0,j=-1,m=strlen(str); while(i<m){ if(j==-1||str[i]==str[j]) next1[++i]=++j; else j=next1[j]; } } /*int kmp() { int i=0,j=0,n=strlen(str),m=strlen(mo); while(i<n){ if(j==-1||str[i]==mo[j]) i++,j++; else j=next1[j]; if(j==m) ans++; } return ans; }*/ int main() { while(cin>>mo1>>mo2){ strcpy(str,mo1); strcat(str,mo2); next1[0]=-1; getnext(); int len=strlen(str); int len1=strlen(mo1); int len2=strlen(mo2); int n=next1[len]; if(n>=len1||n>=len2){ if(n==len1&&n==len2) cout<<mo1<<' '<<n<<endl; else if(len1<len2) cout<<mo1<<' '<<len1<<endl; else cout<<mo2<<' '<<len2<<endl; } else{ bool flag=false; for(int i=0;i<n;i++){ cout<<str[i]; flag=1; } if(flag) cout<<' '; printf("%d\n",n); } } }
B:HDU-3336 Count the string:这道题目是KMP算法,对Next数组的活用。题意就是输入 一个字符串,判断它的子串从0到i(i<=长度) 在主串出现的次数之和。题中也给出了 abab子串有a,ab,aba,abab 分别在主串出现了2,2,1,1 共6次。题目解法,跟Next数组创建有关系,Next数组查询的时候会用到回溯,这就证明了,你所要找的串,之前出现过,这样就可以根据回溯的次数来计算出现次数了。
比如题目中的
序号 0 1 2 3 4
字符串 a b a b
next -1 0 0 1 2
从j=1开始,1回溯一次 sum+=1,j=2的时候也是一次,sum+=1,j=3与j=4时分别回溯两次,sum+=2,sum+=2。所以总共六次。
AC代码:
[code]#include <cstring> #include <iostream> #include <cstdio> #include <fstream> #include <algorithm> using namespace std; const int maxn=2e6+7; const int INF=0x3f3f3f3f; #define M 50015 int next1[maxn]; char str[maxn],mo[maxn]; int ans; void getnext() { int i=0,j=-1,m=strlen(mo); while(i<m){ if(j==-1||mo[i]==mo[j]) next1[++i]=++j; else j=next1[j]; } } /*int kmp() { ans=0; int i=0,j=0,n=strlen(str),m=strlen(mo); while(i<n){ if(j==-1||str[i]==mo[j]) i++,j++; else j=next1[j]; if(j==m) ans++; } return ans; }*/ int main() { int n,t; scanf("%d",&t); while(t--){ memset(next1,0,sizeof next1); int sum=0; scanf("%d %s",&n,mo); next1[0]=-1; getnext(); for(int i=1;i<=n;i++){ int tem=i; while(tem){ sum++; tem=next1[tem]; } } printf("%d\n",sum%10007); } }
C:HDU-4300 Clairewd’s message:一道思维题,给你一张含有26个字母的密码表,对应相应位置的字母a~z。再给你一组不完整的密文+明文,要求输出完整的密文+明文。思路是将密文+明文翻译过来,以原来的密文+明文为主串,翻译后的字符串为模式串,进行扩展KMP匹配。(具体为什么可以好好想想,毕竟思维题),AC代码:
[code]#include <iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> using namespace std; int T,n,len; char s[100010],t[100010],a[28]; int nexts[100010],extand[100010]; void getnexts(char *t) { int len =strlen(t); int a=0; nexts[0]=len; while(a<len-1&&t[a]==t[a+1]) a++; nexts[1]=a; a=1; for(int k=2;k<len;k++) { int p=a+nexts[a]-1,L=nexts[k-a]; if(k-1+L>=p) { int j=max((p-k+1),0); while(k+j<len&&t[k+j]==t[j]) ++j; nexts[k]=j; a=k; } else nexts[k]=L; } } void ekmp(char *s,char *t) { int lens=len,lent=strlen(t),a=0; int minlen=min(lens,lent); while(a<minlen&&s[a]==t[a]) ++a; extand[0]=a; a=0; for(int k=1;k<lens;k++) { int p=a+extand[a]-1,L=nexts[k-a]; if(k-1+L>=p) { int j=max(p-k+1,0); while(k+j<lens&&j<lent&&s[k+j]==t[j]) ++j; extand[k]=j; a=k; } else extand[k]=L; } } int main() { cin>>T; while(T--) { char hash[150]; scanf("%s",a); for(int i=0;i<26;i++) hash[ a[i] ]='a'+i; scanf("%s",s); len=strlen(s); for(int i=0;i<len;i++) t[i]=hash[s[i]]; getnexts(t); ekmp(s,t); int pos=len; for(int i=0;i<len;i++) { if(i+extand[i]>=len&&i>=extand[i]) { pos=i; break; } } for(int i=0;i<pos;i++) printf("%c",s[i]); for(int i=0;i<pos;i++) printf("%c",t[i]); printf("\n"); } }
D:HDU-1238 Substrings:给您一些区分大小写的字母字符串,找出最大的字符串X的长度,这样就可以找到X或它的逆字符串作为任意给定字符串的子字符串,可以直接暴力枚举,具体看代码:
[code]#include <iostream> #include <cstdio> #include <cstring> #include <string> #include <algorithm> using namespace std; int Next[105]; void getNext(string w,int len){ int i = -1,j = 0; memset(Next,0,sizeof(Next)); Next[0] = -1; while(j < len){ if(i == -1 || w[i] == w[j]){ i++,j++; Next[j] = i; } else i = Next[i]; } } bool kmp(string w,int m,string s,int n){ int i = 0,j = 0; getNext(w,m); while(j < n){ if(i == -1 || w[i] == s[j]) i++,j++; else i = Next[i]; if(i >= m){ return true; } } return false; } int main(){ int t; scanf("%d",&t); while(t--){ string str[102]; int ans = 0; int n,i,j,k; scanf("%d",&n); for(i = 0; i < n; i++){ cin >> str[i]; } for(i = 0; i < str[0].length(); i++){ for(j = i; j < str[0].length(); j++){//j从i开始,长度为1的串也要算 string w = str[0].substr(i,j-i+1); for(k = 1; k < n; k++){ string rw = w; reverse(w.begin(),w.end()); if(!kmp(w,w.length(),str[k],str[k].length())&&!kmp(rw,rw.length(),str[k],str[k].length())) break; } if(k >= n){ if(w.length()>ans) ans = w.length(); } } } printf("%d\n",ans); } return 0; }
E:HDU-2328 Corporate Identity:给出若干个串,求最长公共子串,又是一道暴力枚举题。直接暴力枚举公共字串的开始位置和长度,AC代码:
[code]#include <cstdio> #include <cstdlib> #include<cstring> #include <algorithm> using namespace std; char str [20000][20000]; int next1[20000]; char temp[20000]; char sum[20000]; void getnext(char *s1){ int j=0,k=-1; int len=strlen(s1); next1[0]=-1; while(j<len){ if(k==-1||s1[j]==s1[k]){ ++j; ++k; if(s1[j]!=s1[k]) next1[j]=k; else next1[j]=next1 [k]; } else k=next1[k]; } return ; } bool kmp(char *s1,char *s2){ int len1=strlen(s2); int len2=strlen(s1); getnext(s1); int i=0,j=0; while(i<len1){ if(j==-1||s1[j]==s2[i]){ ++i; ++j; } else j=next1[j]; if(j==len2) return true; } return false; } int main() { int n; int i,j,k; while(scanf("%d",&n)==1&&n){ for( i=0;i<n;i++) scanf("%s",str[i]); int len=strlen(str[0]); memset(sum,'\0',sizeof(sum)); for(i=0;i<len;i++){ int ans=0; for(j=i;j<len;j++){ temp[ans]=str[0][j]; ans++; temp[ans]='\0';//注意这里一定要加上,否则出现越界等情况 int flag=1; for(k=1;k<n;k++){ if(!kmp(temp,str[k])){ flag=0; break; } } if(flag){ if(strlen(temp)>strlen(sum)){ strcpy(sum,temp); } else if(strlen(temp)==strlen(sum)&&strcmp(temp,sum)<0) strcpy(sum,temp); } } } if(strlen(sum)>0) printf("%s\n",sum); else printf("IDENTITY LOST\n"); } return 0; }
F:HDU-3374 String Problem:题意:最小最大表示的模板题,可以记住模板,没必要知道原因,想知道也可以一去网上查查。给出多组数据,每组数据给出一个字符串,要求输出这个字符串的最小最大表示的起始位置,然后分别求出在同构串中起始位置的字符出现的次数。思路:最小最大的起始位置直接套用模版即可,然后使用 KMP 的 next 数组求循环节,则次数=长度/循环节长度(循环n次代表可以有n个位置使移动后和最小最大表示相等)。
循环字符串的最小表示法的问题可以这样描述:对于一个字符串S,求S的循环的同构字符串S’中字典序最小的一个。由于语言能力有限,还是用实际例子来解释比较容易:设S=bcad,且S’是S的循环同构的串。S’可以是bcad或者cadb,adbc,dbca。而且最小表示的S’是adbc。最大表示则为dbca
AC代码:
[code]#include<iostream> #include<cstdio> #include<string> #include<cstring> #include<cmath> #include<algorithm> #define INF 0x3f3f3f3f const int N=1000000+5; using namespace std; int Next ; char str ; void getNext(char p[]){ Next[0]=-1; int len=strlen(p); int j=0; int k=-1; while(j<len){ if(k==-1||p[j]==p[k]){ k++; j++; Next[j]=k; } else{ k=Next[k]; } } } int minmumRepresentation(char *str){//最小表示法 int len=strlen(str); int i=0; int j=1; int k=0; while(i<len&&j<len&&k<len){ int temp=str[(i+k)%len]-str[(j+k)%len]; if(temp==0) k++; else{ if(temp>0) i=i+k+1; else j=j+k+1; if(i==j) j++; k=0; } } return i<j?i:j; } int maxmumRepresentation(char *str){//最大表示法 int len=strlen(str); int i=0; int j=1; int k=0; while(i<len&&j<len&&k<len){ int temp=str[(i+k)%len]-str[(j+k)%len]; if(temp==0) k++; else{ if(temp>0) j=j+k+1; else i=i+k+1; if(i==j) j++; k=0; } } return i<j?i:j; } int main(){ while(scanf("%s",str)!=EOF){ getNext(str); int n=strlen(str); int len=n-Next ; int num=1;//数量 if(n%len==0) num=n/len; int minn=minmumRepresentation(str);//最小表示 int maxx=maxmumRepresentation(str);//最大表示 printf("%d %d %d %d\n",minn+1,num,maxx+1,num); } return 0; }
G:HDU-2609 How many:有n个有01组成的字符串,每个字符串都代表一个项链,那么该字符串就是一个环状的结构,求可以经过循环旋转,最后不同的串有多少个。最小表示法的应用,将每个串用最小表示法表示出来,其中有多少个不一样的即为结果。AC代码:
[code]#include <bits/stdc++.h> #define PI acos(-1.0) #define INF 0x3f3f3f3f #define MOD 1000000007 #define EPS 1e-6 #define N 1123456 using namespace std; int n,m,sum,res,flag; char s[101],ss[101]; int minString(char *s) { int i=0,j=1,k=0; int len=strlen(s); while(i<len&&j<len&&k<len) { if(s[(i+k)%len]==s[(j+k)%len])k++; else { if(s[(i+k)%len]>s[(j+k)%len])i=i+k+1; else j=j+k+1; if(i==j)j++; k=0; } } return i<j?i:j; } int main() { int i,j,k,kk,cas,T,t,x,y,z; vector<string>sn; while(scanf("%d",&n)!=EOF) { sn.clear(); for(i=0;i<n;i++) { scanf("%s",s); m=strlen(s); t=minString(s); for(j=0;j<m;j++) ss[j]=s[(j+t)%m]; string st(ss); sn.push_back(st); } sort(sn.begin(),sn.end()); res=1; for(i=1;i<n;i++) if(sn[i]!=sn[i-1]) res++; printf("%d\n",res); } return 0; }
H:FZU-1901 Period II:题意:给出一个字符串,问可以看作由长度多少的子串循环得到,最后一周期可以不全。思路:这题可以KMP,next数组求公共的子串,取next[len],之前求循环节的知道,len-next[len]就是最短的循环节,依次递归地求next用子串长度减就是子串的最短循环节,加上原来的子串外的那部分就是新的循环节(子串外部分也是子串的循环节)这个在纸上画一画基本都出来了。两者加在一起就是len-nexts[buf],其中buf为依次递归求nexts的值。AC代码:
[code]#include <iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; char s[2000100]; int nexts[2000100],sum[2000100]; int len,n,num; void getnexts(char *s) { int i=0,j=-1; nexts[0]=-1; while(i<len) { if(s[i]==s[j]||j==-1) { nexts[++i]=++j; } else j=nexts[j]; } } int main() { cin>>n; for(int i=1;i<=n;i++) { scanf("%s",s); len=strlen(s); getnexts(s); num=0; int buf=len; while(buf) { buf=nexts[buf]; sum[num++]=len-buf; } printf("Case #%d: %d\n",i,num); for(int j=0;j<num-1;j++) printf("%d ",sum[j]); printf("%d\n",sum[num-1]); } }
相关文章推荐
- KMP&扩展KMP&Manacher算法基础与习题(第三更)
- 基础算法 扩展KMP
- 字符串基础_扩展KMP
- 最小(大)表示法习题 -- 来自[kuangbin带你飞]专题十六 KMP & 扩展KMP & Manacher
- 数论扩展欧几里德基础习题(4.15)
- 第二部分面向对像基础第五章下半部分与习题总结
- 第二部分面向对像基础第五章下半部分与习题总结
- Linux运维 第二阶段 (一)linux基础概念及常用命令
- 理解WebKit和Chromium: Chromium插件和扩展基础
- HDU4300 Clairewd’s message 扩展kmp
- 泛型算法基础习题
- c# 扩展方法奇思妙用基础篇五:Dictionary<TKey, TValue> 扩展
- [置顶] 机器学习面试基础知识 & 扩展-01
- hdu3613 扩展KMP
- linux基础学习 第二-四单元练习
- 扩展:通过SPRING INITIALIZR工具产生基础项目搭建SpringBoot+maven项目
- dp基础习题(4.13)
- 前端基础进阶(八):详细图解jQuery对象,以及如何扩展jQuery插件
- 【linq基础】4.扩展方法
- 网络习题课——用函数解决问题之基础知识