HDU 6153 A Secret(拓展KMP)

HDU 6153 A Secret

题意

先翻过来,后缀先变前缀(不想描述后缀啦^_^)

给出两个串,这里我们记作S串和T串

对于T串的一个前缀t串,求出t串在S串中出现了多少次,这个小t串对答案的贡献就是”次数 * t的长度”

求T串所有符合条件的前缀对答案的贡献和

解决

  1. 对拓展KMP没经验^_^,所以现场没写出来,惭愧
  2. 不管三七二十一,先翻转过来再说…
  3. 重点,我们求出在S串里,以下标i开始,有多长的字符串可以与T串的前缀完全匹配
  4. 拓展KMP的extend数组恰好可以找到这个长度
  5. 知道了每一个位置的匹配长度.我们知道,如果我们匹配到了一个长度为n的串.这个串的每一个前缀的长度都会对答案有一次贡献,而答案恰恰是这些一次次贡献的和.
  6. 我们利用等差数列求和公式,即可快速求解…
  7. 为方便理解,用样例来演示一下extend数组
S=abababab
T=aba
sub:          0 1 2 3 4 5 6 7
S(reversed):  b a b a b a b a
extend[i]     0 3 0 3 0 3 0 1

extend[1]=3表示:以a(S[1])打头,可以有3个长度去与T串完全匹配
extend[7]=1...同理
如果想看extend数组的值是怎么变化的,取消掉注释就可以看到
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
#define de(x) cout << #x << "=" << x << endl
const int maxn = 1e6+5;
const int MOD = 1e9 + 7;

int n;
char S[maxn],T[maxn];
int fail[maxn],extend[maxn];    //next和fail其实是一个意思,都表示失败后回跳到哪里
                                //但是不知道为什么用next的时候,HDU上会报编译错误/(ㄒoㄒ)/~~


//这里用的是kuangbin大神的模板

//next[i]:x[i...m-1]与x[0...m-1]的最长公共前缀
//extend[i]:y[i...n-1]与x[0...m-1]的最长公共前缀

void pre_EKMP(char x[],int m,int fail[])
{
    fail[0]=m;
    int j=0;
    while(j+1<m && x[j]==x[j+1]) j++;
    fail[1]=j;
    int k=1;
    for(int i=2;i<m;i++)
    {
        int p=fail[k]+k-1;
        int L=fail[i-k];
        if(i+L<p+1) fail[i]=L;
        else
        {
            j=max(0,p-i+1);
            while(i+j<m && x[i+j]==x[j]) j++;
            fail[i]=j;
            k=i;
        }
    }
}


void EKMP(char x[],int m,char y[],int n,int fail[],int extend[])
{
    pre_EKMP(x,m,fail);
    int j=0;
    while(j<n&&j<m&&x[j]==y[j]) j++;
    extend[0]=j;
    int k=0;
    for(int i=1;i<n;i++)
    {
        int p=extend[k]+k-1;
        int L=fail[i-k];
        if(i+L<p+1) extend[i]=L;
        else
        {
            j=max(0,p-i+1);
            while(i+j<n&&j<m&&y[i+j]==x[j]) j++;
            extend[i]=j;
            k=i;
        }
    }
}

int main()
{
    int cases;
    scanf("%d",&cases);
    while(cases--)
    {
        scanf("%s",S);
        scanf("%s",T);
        int len_s=strlen(S) , len_t=strlen(T);
        reverse(S,S+len_s);
        reverse(T,T+len_t);
        //de(S);de(T);

        memset(fail,0,sizeof(fail));
        memset(extend,0,sizeof(extend));

        EKMP(T,len_t,S,len_s,fail,extend);

        /*for(int i=0;i<len_s;i++)
            printf("%d%c",extend[i],i==len_s-1?'\n':' ');*/
        long long ans=0,tmp,n;
        for(int i=0;i<len_s;i++)
        {
            if(extend[i])
            {
                n=extend[i]%MOD;
                tmp=(n*(n+1)/2)%MOD;        //等差数列求和:n(n+1)/2
                ans=(ans+tmp);
                if(ans>MOD) ans-=MOD;
            }
        }
        cout<<ans<<endl;

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值