HDU 4333:Revolving Digits KMP+扩展KMP

扩展KMP的姿势
扩展KMP可以在 O(n) 的复杂度内求这样一个问题:
给定两个串 S,T ,设 n=|S|,m=|T| S 中的每个后缀与T的最长公共前缀,用 extend[i] 表示。即 extend[i]=LCP(S[i..n],T)
算法过程:
类似的,我们令 next[i] 表示 T 中的每个后缀与T的最长公共前缀,即 next[i]=LCP(T[i..m],T)
k 表示当前k+next[k]1最大的 k ,并令p=k+next[k]1
假设当前要计算 extend[i] ,易知 S[k..p]=T[1..pk+1]
则有 S[i..p]=T[ik+1,pk+1]
L=next[ik+1] ,
L<pi+1 ,则 extend[i]=L
否则,先令 extend[i]=pi+1 (注意 pi+1 不能 <0 <script type="math/tex" id="MathJax-Element-7785"><0</script>,如果 <0 <script type="math/tex" id="MathJax-Element-7786"><0</script>就赋为 0 ),然后从S[i+next[i]] T[next[i]+1] 向后匹配,直到失配为止。
算法结束后,如果当前 i+next[i]>k+next[k] ,则更新 k
求解next数组的过程与本过程类似,其实就是 T 自身的扩展KMP过程。
扩展KMP算法是十分优秀的,常见的应用有:
1.求最长公共前缀。
2.求解重复子串的长度:
如串abababc,next[3]=4,next[5]=2,重复子串 ababab 的长度即 i+next[i]1=6 ,再如串 ababa,next[3]=3,next[5]=1 ,重复子串 ababa 的长度即 i+next[i]1=5 。所以重复子串的长度 = 端点处的i+next[i]1,若最后一个循环节不完整也将其算在内。

然后这道题,我们可以把原串补在后面一遍,然后求出 next 数组,若 next[i]>=len ,证明两个串相等,否则我们只需要比较 s[next[i]+1] s[i+next[i]] 的大小,就能得出两个串的大小关系。
然后至于重复的串,我们可以用kmp来求一下最小循环节,将三个答案都除以最小循环节出现的个数就好了。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a,b,c,len;
int p[100005],q[100005];
char s[200005];
int main()
{
    int testcase;
    scanf("%d",&testcase);
    for (int T=1;T<=testcase;T++)
    {
        scanf("%s",s+1);
        int j=0; p[1]=0;
        len=strlen(s+1);
        for (int i=2;i<=len;i++)
        {
            while (j&&s[j+1]!=s[i]) j=p[j];
            if (s[j+1]==s[i]) j++;
            p[i]=j;
        }
        int tmp=len%(len-p[len])==0?len/(len-p[len]):1;
        for (int i=1;i<=len;i++) s[i+len]=s[i]; 
        len<<=1;
        int p=1;
        q[1]=len;
        while (p<=len&&s[p]==s[p+1]) p++;
        q[2]=p-1; 
        int k=2;
        for (int i=3;i<=len>>1;i++)
        {
            int p=k+q[k]-1;
            q[i]=min(q[i-k+1],max(p-i+1,0));
            while (i+q[i]<=len&&s[q[i]+1]==s[i+q[i]]) q[i]++;
            if (i+q[i]>k+q[k]) k=i;
        }
        len>>=1;
        a=b=c=0;
        for (int i=1;i<=len;i++)    
            if (q[i]>=len) b++;
            else if (s[q[i]+1]>s[i+q[i]]) a++;
            else c++;
        printf("Case %d: %d %d %d\n",T,a/tmp,b/tmp,c/tmp);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值