HDU6153-A Secret

来源:互联网 发布:淘宝上好的家具店 编辑:程序博客网 时间:2024/06/05 08:13

A Secret

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 256000/256000 K (Java/Others)
Total Submission(s): 254 Accepted Submission(s): 105

Problem Description
Today is the birthday of SF,so VS gives two strings S1,S2 to SF as a present,which have a big secret.SF is interested in this secret and ask VS how to get it.There are the things that VS tell:
Suffix(S2,i) = S2[i…len].Ni is the times that Suffix(S2,i) occurs in S1 and Li is the length of Suffix(S2,i).Then the secret is the sum of the product of Ni and Li.
Now SF wants you to help him find the secret.The answer may be very large, so the answer should mod 1000000007.

Input
Input contains multiple cases.
The first line contains an integer T,the number of cases.Then following T cases.
Each test case contains two lines.The first line contains a string S1.The second line contains a string S2.
1<=T<=10.1<=|S1|,|S2|<=1e6.S1 and S2 only consist of lowercase ,uppercase letter.

Output
For each test case,output a single line containing a integer,the answer of test case.
The answer may be very large, so the answer should mod 1e9+7.

Sample Input
2
aaaaa
aa
abababab
aba

Sample Output
13
19
Hint

case 2:
Suffix(S2,1) = “aba”,
Suffix(S2,2) = “ba”,
Suffix(S2,3) = “a”.
N1 = 3,
N2 = 3,
N3 = 4.
L1 = 3,
L2 = 2,
L3 = 1.
ans = (3*3+3*2+4*1)%1000000007.

Source
2017中国大学生程序设计竞赛 - 网络选拔赛

题目大意:给出两个字符串S1,S2,求S2的所有后缀在S1中出现的次数与其长度的乘积之和。
解题思路:感谢大家的指正~~~
kmp匹配的时候会跳过一些字符,而这些字符可能会与前缀匹配,所以原先的做法会漏掉一些情况,比如下面两个串。

baabaacaabaa

所以为了不重不漏,就要用扩展kmp求出最长公共前缀,nxt[i]表示的是pat[i...plen1]pat[0...plen1]的最长公共前缀,extend[i] 表示的是txt[i...tlen1]pat[0...plen1]的最长公共前缀,这样遍历一遍文本串,每一位对答案的贡献就是1++extend[i]=extend[i]×(extend[i]+1)/2,累加即可。

#include<iostream>#include<cstdio>#include<cmath>#include<cstring>#include<vector>#include<algorithm>#include<string>using namespace std;typedef long long LL;const int MAXN=1e6+5;const int MOD=1e9+7;char txt[MAXN],pat[MAXN];int nxt[MAXN],extend[MAXN];LL ans;void getnxt(char *p){    int plen=strlen(p);    nxt[0]=plen;    int j=0;    while(j+1<plen&&p[j]==p[j+1]) j++;    nxt[1]=j;    int k=1;    for(int i=2;i<plen;i++)    {        int pos=nxt[k]+k-1;        int L=nxt[i-k];        if(i+L<pos+1) nxt[i]=L;        else        {            j=max(0,pos-i+1);            while(i+j<plen&&p[i+j]==p[j]) j++;            nxt[i]=j;            k=i;        }    }}void e_kmp(char *p,char *t){    int j=0;    int plen=strlen(p);    int tlen=strlen(t);    while(j<tlen&&j<plen&&p[j]==t[j]) j++;    extend[0]=j;    int k=0;    for(int i=1;i<tlen;i++)    {        int pos=extend[k]+k-1;        int L=nxt[i-k];        if(i+L<pos+1) extend[i]=L;        else        {            j=max(0,pos-i+1);            while(i+j<tlen&&j<plen&&t[i+j]==p[j]) j++;            extend[i]=j;            k=i;        }    }}int main(){    int T;    scanf("%d",&T);    while(T--)    {        ans=0;        scanf("%s%s",txt,pat);        int tlen=strlen(txt);        reverse(txt,txt+tlen);        int plen=strlen(pat);        reverse(pat,pat+plen);        getnxt(pat);        e_kmp(pat,txt);//        for(int i=0;i<tlen;i++)//        {//            cout<<extend[i]<<" ";//        }//        cout<<endl;        for(int i=0;i<tlen;i++)        {            ans=(ans+((LL)extend[i]*(extend[i]+1)/2)%MOD)%MOD;        }        printf("%lld\n",ans);    }    return 0;}

请忽略以下话:
S1,S2倒转,用KMP求得S2fail数组,在S1上滑动的同时,累加结果。比如S1=aaaaS2=aaa,求得S2fail数组为0,0,1,2fail[j]的含义是S2j位置之前的最长的前缀等于后缀的长度,每当匹配失败(模式串匹配完也可以说是一种“失败”)时,计算一下当前匹配对结果的贡献(长度为1j的前缀之和),即1+2++j=j(j+1)/2,然后就可以将fail[j]长度的前缀移至相同的后缀,这样增加的种数是1+2++fail[j],以此类推,直到fail为零。
这样第一次匹配为

aaaaaaa

此时j为3,那么就加上3(3+1)/2=6,然后继续匹配
aaaaaaa

第二次匹配,在加上3(3+1)/2=6
值得注意的是,由于此时文本已匹配完毕,但S2的前缀还有可能继续跟S1匹配,所以结束后还要按匹配失败继续判断,此时j=2,代表aaa的长度为2的前缀可以继续与其长度为2的后缀匹配,那么就要再加上2(2+1)/2=3
aaaaaaa

接着继续此时j=2fail[j]=1,那么就要再加上1(1+1)/2=1
aaaaaaa

因为fail[1]=0,所以结束
故最终结果为6+6+3+1=16

错误的代码:

#include<iostream>#include<cstdio>#include<algorithm>#include<cstring>using namespace std;typedef long long LL;const int MAXN=2e6+5;const int MOD=1e9+7;char pat[MAXN],txt[MAXN];int fail[MAXN];LL ans;void getfail(char *p){    int plen=strlen(p);    fail[0]=0,fail[1]=0;//递推初值    for(int i=1;i<plen;++i)    {        int j=fail[i];        while(j&&p[i]!=p[j])  j=fail[j];        if(p[i]==p[j])  fail[i+1]=j+1;        else  fail[i+1]=0;    }}void kmp(char *t,char *p){    int tlen=strlen(t),plen=strlen(p);    LL j=0;//当前结点编号    for(int i=0;i<tlen;++i)//文本串当前指针    {        //顺着失配边走,直到可以匹配        while(j&&p[j]!=t[i])        {            ans=(ans+(j*(j+1)/2)%MOD)%MOD;            j=fail[j];        }        if(p[j]==t[i])        {            ++j;        }        if(j==plen)        {            ans=(ans+j*(j+1)/2)%MOD;            j=fail[j];        }    }    while(j>=1)    {        ans=(ans+(j*(j+1)/2)%MOD)%MOD;        j=fail[j];    }}int main(){    int T;    scanf("%d",&T);    while(T--)    {        ans=0;        scanf("%s%s",txt,pat);        int tlen=strlen(txt);        reverse(txt,txt+tlen);        int plen=strlen(pat);        reverse(pat,pat+plen);        getfail(pat);//        for(int i=0;i<=plen;++i)//            printf("%d ",fail[i]);//        printf("\n");        kmp(txt,pat);        printf("%lld\n",ans);    }    return 0;}/*2aaaaaaaabababababa1319*/