hdu 6153 拓展kmp

类似于模板题了

扩展KMP:
    给出模板串A和子串B,长度分别为lenA和lenB,要求在线性时间内,对于每个A[i](0 <= i < lenA),

    求出A[i..lenA-1]与B的最长公共前缀长度,记为ex[i](或者说,ex[i]为满足A[i..i+z-1]==B[0..z-1]的最大的z值)。

    扩展KMP可以用来解决很多字符串问题,如求一个字符串的最长回文子串和最长重复子串。

【算法】
    
    设next[i]为满足B[i..i+z-1]==B[0..z-1]的最大的z值(也就是B的自身匹配)。设目前next[0..lenB-1]与ex[0..i-1]均已求出,要用它们来求ex[i]的值。

    设p为目前A串中匹配到的最远位置,k为让其匹配到最远位置的值(或者说,k是在0<= i0 < i的所有i0值中,使i0+ex[i0]-1的值最大的一个,p为这个最大值,即k+ex[k]-1),
    
    显然,p之后的所有位都是未知的,也就是目前还无法知道A[p+1..lenA-1]中的任何一位和B的任何一位是否相等。
    
    根据ex的定义可得,A[k..p]==B[0..p-k],因为i>k,所以又有A[i..p]==B[i-k..p-k],设L=next[i-k],则根据next的定义有B[0..L-1]==B[i-k..i-k+L-1]。考虑i-k+L-1与p-k的关系:
    
    (1)i-k+L-1 < p-k,即i+L<=p。这时,由A[i..p]==B[i-k..p-k]可以得到A[i..i+L-1]==B[i-k..i-k+L-1],又因为B[0..L-1]==B[i-k..i-k+L-1]所以A[i..i+L-1]==B[0..L-1],这就说明ex[i]>=L。又由于next的定义可得,

A[i+L]必然不等于B[L](否则A[i..i+L]==B[0..L],因为i+L<=p,所以A[i..i+L]==B[i-k..i-k+L],这样B[0..L]==B[i-k..i-k+L],故next[i-k]的值应为L+1或更大),这样,可以直接得到ex[i]=L!
    
    (2)i+k-L+1>=p-k,即i+L>p。这时,首先可以知道A[i..p]和B[0..p-i]是相等的(因为A[i..p]==B[i-k..p-k],而i+k-L+1>=p-k,由B[0..L-1]==B[i-k..i-k+L-1]可得B[0..p-i]==B[i-k..p-k],即A[i..p]==B[0..p-i]),然

后,对于A[p+1]和B[p-i+1]是否相等,目前是不知道的(因为前面已经说过,p是目前A串中匹配到的最远位置,在p之后无法知道任何一位的匹配信息),因此,要从A[p+1]与B[p-i+1]开始往后继续匹配(设j为目前

B的匹配位置的下标,一开始j=p-i+1,每次比较A[i+j]与B[j]是否相等,直到不相等或者越界为止,此时的j值就是ex[i]的值)。在这种情况下,p的值必然会得到延伸,因此更新k和p的值。
    
    边界:ex[0]的值需要预先求出,然后将初始的k设为0,p设为ex[0]-1。

对于求next数组,也是“自身匹配”,类似KMP的方法处理即可。唯一的不同点也在边界上:可以直接知道next[0]=lenB,next[1]的值预先求出,然后初始k=1,p=ex[1]。

需要严重注意的是,在上述的情况(2)中,本该从A[p+1]与B[p-i+1]开始匹配,但是,若p+1 < i,也就是 p-i+1 <0(这种情况是有可能发生的,当ex[i-1]=0,且前面的ex值都没有延伸到i及以后的时候)的话,需要将A、B的下标都加1(因为此时p必然等于i-2,如果A、B的下标用两个变量x、y控制的话,x和y都要加1)!!

【时间复杂度分析】

    在KMP和扩展KMP中,不管是A串还是B串,其匹配位置都是单调递增的,故总时间复杂度是线性的,都为O(lenA + lenB)(只是扩展KMP比KMP的常数更大一些)。

【应用】

    KMP和扩展KMP在解决字符串问题中有大用。很多看上去很猥琐的字符串问题,都可以归结到这两种算法之中。另外,这里的“字符串”可以延伸为一切类型的数组,而不仅仅是字符数组。

这个题问第二个串的后缀在第一个串出现的次数*权值
两个串反过来就变成求最长公共前缀的问题了,而求最长公共前缀就是拓展kmp了因为长的必定对短的有贡献,所以累计一遍extend就好了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;   //字符串长度最大值  
int nxt[maxn],ex[maxn]; //ex数组即为extend数组 
const int mod = 1e9+7; 
//预处理计算nxt数组  
void GETNEXT(char *str)  
{  
    int i=0,j,po,len=strlen(str);  
    nxt[0]=len;//初始化nxt[0]  
    while(str[i]==str[i+1]&&i+1<len)//计算nxt[1]  
    i++;  
    nxt[1]=i;  
    po=1;//初始化po的位置  
    for(i=2;i<len;i++)  
    {  
        if(nxt[i-po]+i<nxt[po]+po)//第一种情况,可以直接得到nxt[i]的值  
        nxt[i]=nxt[i-po];  
        else//第二种情况,要继续匹配才能得到nxt[i]的值  
        {  
            j=nxt[po]+po-i;  
            if(j<0)j=0;//如果i>po+nxt[po],则要从头开始匹配  
            while(i+j<len&&str[j]==str[j+i])//计算nxt[i]  
            j++;  
            nxt[i]=j;  
            po=i;//更新po的位置  
        }  
    }  
}  
//计算extend数组  
void EXKMP(char *s1,char *s2)  
{  
    int i=0,j,po,len=strlen(s1),l2=strlen(s2);  
    GETNEXT(s2);//计算子串的nxt数组  
    while(s1[i]==s2[i]&&i<l2&&i<len)//计算ex[0]  
    i++;  
    ex[0]=i;  
    po=0;//初始化po的位置  
    for(i=1;i<len;i++)  
    {  
        if(nxt[i-po]+i<ex[po]+po)//第一种情况,直接可以得到ex[i]的值  
        ex[i]=nxt[i-po];  
        else//第二种情况,要继续匹配才能得到ex[i]的值  
        {  
            j=ex[po]+po-i;  
            if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配  
            while(i+j<len&&j<l2&&s1[j+i]==s2[j])//计算ex[i]  
            j++;  
            ex[i]=j;  
            po=i;//更新po的位置  
        }  
    }  
}  

char s1[maxn],s2[maxn];
int ans[maxn];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s %s",s1,s2);
        int len1=strlen(s1),len2=strlen(s2);
        reverse(s1,s1+len1),reverse(s2,s2+len2);
        memset(ans,0,sizeof(ans));
        EXKMP(s1,s2);
        for(int i=0;i<len1;i++)
        {
            ans[ex[i]]++;
        }
        ll tol=0;
        for(ll i=1;i<=len1;i++)
        {
            if(ans[i])
            {
                tol=tol+(1ll*ans[i]*(i+1)*i/2%mod);
                tol%=mod;
            }
        }
        printf("%lld\n",tol );
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值