HDU 6153 A Secret【KMP||扩展KMP】

本文介绍了一种针对两个字符串S1和S2的特殊秘密求解算法,该算法通过计算S2的所有后缀在S1中出现的次数与后缀长度的乘积之和来揭示隐藏的秘密。提供两种实现方案:一种基于拓展KMP算法,另一种基于传统KMP算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


A Secret

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

Problem Description

Today is the birthday of SF,so VS gives two strings S1,S2 to SF as a present,whichhave a big secret.SF is interested in this secret and ask VS how to getit.There are the things that VS tell:
Suffix(S2,i) = S2[i...len].Ni is the times that Suffix(S2,i) occursin S1 and Li is the length of Suffix(S2,i).Then the secret is the sum of theproduct of Ni and Li.
Now SF wants you to help him find the secret.The answer may be verylarge, so the answer should mod 1000000007.

 

 

Input

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

 

 

Output

For each test case,output a single line containing a integer,the answer of testcase.

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

 

case2:

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.


【题意】


给出两个串S和T,求串T的后缀在串S中出现的次数*后缀长度乘积的和。


【思路】


设母串为S,子串为T。


方法一:拓展kmp算法


我们先把两个串都反一下,那么就变成串T的前缀在串S中出现的次数*后缀长度乘积的和。


我们知道拓展kmp可以算出extend数组,而extend[i]表示S[i,lens-1]与T的最长公共前缀。


那么显然每个extend[i]对答案的贡献为(extend[i]*(extend[i]+1))/2。


举个例子:


S串: abbabbab


T串: abbc


extend[3]=3,那么长度为1,2,3的“a”,"ab","abb"各匹配一次,贡献为(1+2+3),即(1+2+...+extend[i]),即(extend[i]*(extend[i]+1))/2。

#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scanf("%d",&T);while(T--)

typedef long long ll;
const int maxn = 1000005;
const ll mod = 1e9+7;
const int INF = 0x3f3f3f3f;

int nex[maxn];
int extend[maxn];

void pre_exkmp(char *b)
{
    int len=strlen(b);
    nex[0]=len;
    int j=0;
    while(j+1<len&&b[j]==b[j+1]) j++;
    nex[1]=j;
    int k=1;
    for(int i=2;i<len;i++)
    {
        int p=nex[k]+k-1;
        int L=nex[i-k];
        if(i+L<p+1) nex[i]=L;
        else
        {
            j=max(0,p-i+1);
            while(i+j<len&&b[i+j]==b[j]) j++;
            nex[i]=j;
            k=i;
        }
    }
}

void exkmp(char *a,char *b)
{
    pre_exkmp(b);
    int j=0;
    int lena=strlen(a);
    int lenb=strlen(b);
    while(j<lena&&j<lenb&&a[j]==b[j]) j++;
    extend[0]=j;
    int k=0;
    for(int i=1;i<lena;i++)
    {
        int p=extend[k]+k-1;
        int L=nex[i-k];
        if(i+L<p+1) extend[i]=L;
        else
        {
            j=max(0,p-i+1);
            while(i+j<lena&&j<lenb&&a[i+j]==b[j]) j++;
            extend[i]=j;
            k=i;
        }
    }
}

ll add(ll x)
{
    ll ans=x%mod*((x+1)%mod)%mod*500000004%mod;   //有除法,用到逆元
    return ans;
}

char a[maxn];
char b[maxn];

int main()
{
    rush()
    {
        scanf("%s%s",a,b);
        int lena=strlen(a);
        int lenb=strlen(b);
        reverse(a,a+lena);
        reverse(b,b+lenb);
        exkmp(a,b);
        ll ans=0;
        for(int i=0;i<lena;i++)
        {
            if(extend[i])
            {
                ans=(ans+add(extend[i]))%mod;
            }
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

方法二: kmp算法


可以先参考一下这道题的做法: 1277 字符串中的最大值


同样先把S串和T串反一下。


在这道题里,我们先用KMP算法求出T长度为i的前缀在S中出现的次数。


但由于kmp算法中由于nex数组的存在,回溯时跳过了一部分位置,所以我们需要把它们加回去。


根据nex数组的定义,nex[i]表示既是S[0,i-1]的前缀又是其后缀的串的最大长度。


那么举个例子,对于串: “abcdab”


nex[6]=2,由于这段长度被跳过了,那么长度为2的需要加上这一部分。即:


dp[nex[i]]+=dp[i]


建议自己模拟一下。


然后累加一下就可以了。


#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scanf("%d",&T);while(T--)

typedef long long ll;
const int maxn = 1000005;
const ll mod = 1e9+7;
const int INF = 0x3f3f3f;
const double eps = 1e-9;

int nex[maxn];
ll dp[maxn];
char s[maxn];
char t[maxn];

void kmp_pre(char *b)
{
    int i=0,j=-1;
    int len=strlen(b);
    nex[0]=-1;
    while(i<len)
    {
        while(j!=-1&&b[i]!=b[j]) j=nex[j];
        if(b[++i]==b[++j]) nex[i]=j;
        else nex[i]=j;
    }
}

void kmp(char *a,char *b)
{
    int i=0,j=0;
    kmp_pre(b);
    int lena=strlen(a),lenb=strlen(b);
    while(i<lena)
    {
        while(j!=-1&&a[i]!=b[j]) j=nex[j];
        i++,j++;
        dp[j]++;               //长度为j的前缀匹配数目加1
        if(j>=lenb)
        {
            j=nex[j];
        }
    }
}

int main()
{
    rush()
    {
        mst(dp,0);
        scanf("%s%s",s,t);
        int lens=strlen(s);
        int lent=strlen(t);
        reverse(s,s+lens);
        reverse(t,t+lent);
        kmp(s,t);
        ll ans=0;
        for(int i=lent;i>=1;i--)
        {
            dp[nex[i]]+=dp[i];
            ans=(ans+dp[i]*i)%mod;
        }
        printf("%I64d\n",ans);
    }
    return 0;
}



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值