KMP(POJ2403、POJ2752、HDU3336、HDU3689)

给大家献上大神的讲解:
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);
 }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值