给大家献上大神的讲解:
https://blog.csdn.net/v_july_v/article/details/7041827
NEXT数组:
void getnexta(char s[]){//s是长的那个串
memset(nexta,0,sizeof(nexta));
int n=strlen(s);
int k=-1,j=0;
nexta[0]=-1;
while(j<n){
if(k==-1||s[k]==s[j]){
nexta[j+1]=k+1;
j++;
k++;
}
else k=nexta[k];
}
}
KMP代码:
int kmp(char s[],char t[]){ //能匹配则返回首次匹配的位置
getnexta(t);
int n=strlen(s),m=strlen(t);
int i=0,j=0;
while(i<n&&j<m){
if(j==-1||s[i]==t[j]){
i++;
j++;
}
else j=nexta[j];
}
if(j==m) return i-j;
else return -1;
}
POJ2406
题意:给出一个串s,求其最小循环节长度。
思路:根据next数组的性质可知,若l%(l-next[l])==0,最小循环节必为p[next[i]]到p[l-1],l为串的长度,否则最小循环节为字串本身。
#include<iostream>
#include<cstring>
using namespace std;
int nexta[1000006];
char t[1000006];
void getnexta(char s[]){
memset(nexta,0,sizeof(nexta));
int n=strlen(s);
int k=-1,j=0;
nexta[0]=-1;
while(j<n){
if(k==-1||s[k]==s[j]){
nexta[j+1]=k+1;
j++;
k++;
}
else k=nexta[k];
}
}
int main(){
while(cin>>t){
getnexta(t);
for(int i=0;i<7;i++)
cout<<i<<" "<<nexta[i]<<endl;
if(t[0]=='.') break;
int ans=1;
int l=strlen(t);
if(l%(l-nexta[l]) == 0) {//**循环次数
ans=l/(l-nexta[l]);
}
cout<<ans<<endl;
}
}
POJ2752
题意:给你一个串,如果这个串存在一个长度为n的前缀串,和长度为n的后缀串,并且这两个串相等,则输出他们的长度n。求出所有的长度n。
思路:KMP中的get_next()。对前缀函数next[]又有了进一步的理解,str[1]~~str[next[len]]中的内容一定能与str[1+len-next[len]]~~str[len]匹配(图1)。然后呢我们循环地利用next,由于next的性质,即在图2中若左红串与左绿串匹配,则左红串比与右绿串匹配,因为图1的左红串与右红串是完全相等的。可以保证,每一次得出的字串都能匹配到最后一个字母,也就是得到一个前缀等于后缀。只不过这个字符串的长度在不断地减小罢了。
int main()
{
while(scanf("%s",str)!= EOF)
{
len = strlen(str);
get_next();
ans[0] = len;
int n = 0, i = len;
while(next[i]> 0)
{
n++;
ans[n] = next[i];
i = next[i];
}
for(i = n; i >= 0; i--)
printf("%d ", ans[i]);
printf("\n");
}
return 0;
}
hdu3336
题目大意:
给定一个字符串s,求s的每个前缀在此字符串中出现的次数,然后对次数求和,然后再对10007取模,就是要输出的答案。
思路:next数组存放的是字符串的前缀和后缀能匹配的字符个数的最大值。
对于i,(1 <= i <=n),n是字符串s的长度
如果next[i] == 0,则表示 由s的前i个字符组成的字符串的所有后缀肯定和其前缀不匹配。
否则 由s的前i个字符组成的字符串存在某个前缀和后缀匹配的情况,也就是该前缀的出现的次数应该加上1。
定义f[i]为 在由s的前i个字符组成的字符串subs中,subs所有前缀出现的次数之和。
for(int i=1;i<=n;i++){
dp[i]=dp[next[i]]+1;
sum=(sum+dp[i])%10007;
}
cout<<sum<<endl;
HDU3689
题目描述:
大概的意思就是根据无限猴子定理,无限只猴子坐在打字机旁瞎敲,总有一个能敲出莎士比亚文集。现在给你一个打字机和一只猴子,打字机的每个按钮(共n个)上的字母及猴子按下这个按钮的概率已知,而且猴子只能按m下按钮,又给定一个串,问猴子打出的乱码中含这个串的概率。
其中n<=26,m<=1000,多组数据,以n=0,m=0结束。以百分数形式输出,保留小数点后2位。
样例:
输入:
4 10
w 0.25
o 0.25
r 0.25
d 0.25
word
2 10
a 1.0
b 0.0
abc
2 100
a 0.312345
b 0.687655
abab
0 0
输出:
2.73%
0.00%
98.54%
解题思路:
对于第一组数据(work)
很显然算法是:0.250.250.250.257*100%=2.73%;
而对于第三组(abab)就不成立了,为什么呢?
显然是因为第一组没有重复的字母出现,也就是说,如果你的猴子恰好打下了aba,然后它又不幸地打下了a那么也不算太糟,至少你只需要再打一个bab就可以完成任务了。而对于第一只猴子就没有那么幸运了,如果它打下了wor又不幸地打下了r,那么它必须再打下work才能完成任务。
也就是说,即使你打下了错误的字母,你也有可能创造了一个前缀。
所以说我们只需要求出一个错误的字符创造出的前缀是谁,就可以更新这个前缀出现的概率了。
那么考虑用dp[i][j]表示在猴子打下第i个字母时字符串完成到j的匹配的概率。
而这个由错误创造的前缀是谁,这是不是KMP。
然而这和普通的KMP不一样,或者我学了假的KMP,这次kmp的next数组存的是这个模式串第i位的值对应存在的前缀的位置,也就是说,这次是成功指针,而非失配指针。
dp方程就出来了:dp[这一次敲击][最长匹配的新字符最长前缀]=∑dp[上一次敲击][最长匹配](枚举新字符是谁,再进行前缀操作)
int main(){
while(scanf("%d%d",&n,&m)){
if(!n&&!m) break;
memset(dp,0,sizeof(dp));
getchar();
for(int i=0;i<n;i++)
scanf("%c %lf\n",&c[i],&p[i]);
scanf("%s",s);
len=strlen(s);
getnexta();
dp[0][0]=1;
for(int i=0;i<m;i++){
for(int j=1;j<=len;j++){
for(int k=0;k<n;k++){
int pos=j-1;
while(pos>=0&&s[pos]!=c[k])
pos=nexta[pos];
pos++;
dp[i+1][pos]+=dp[i][j-1]*p[k];
}
}
}
double ans=0.0;
for(int i=1;i<=m;i++)
ans+=dp[i][len];
printf("%.2lf%%\n",ans*100);
}
}