HDU 6153 A Secret 经典扩展KMP
http://acm.hdu.edu.cn/showproblem.php?pid=6153
题目大意是:
给定两个串 S1,S2
计算串 S2 的所有后缀在 S1 中出现的次数。
输出各长度与其出现次数乘积的 和。
我们对 S1,S2 按照中心对称轴进行一次对称操作。
使得:
swap(S1[i],S1[j]),其中i+j=S1.lengthswap(S2[i],S2[j]),其中i+j=S2.length
因为对称后。后缀变为前缀。
此时等效于计算 S2 的所有前缀在 S1 中出现的次数。
利用扩展KMP算法。为们可以快速计算出:
S1的所有后缀。与S2的公共前缀
我们记。
ret[i]
是
S1
第
i
个位置开始的后缀与S2 的公共前缀的长度。
那么
i
位置开始的后缀对长度小于等于ret[i] 的
S2
的前缀的数量都有贡献。
定义桶数组 ans[]
我们利用桶数组来统计 ret[] 各个取值出现的次数。
那么长度为
i
的后缀出现的次数为:∑k=iS1.lengthans[k]
搞定。通过预处理前缀和。总时间复杂度 O(|S1|+|S2|) 解决。
下面是代码。内附扩展kmp模版
#include <algorithm>
#include <string.h>
#include <stdio.h>
#define MAXN 1000005
using namespace std;
typedef long long LL;
const LL P=1e9+7;
//计算 a对b的所有后缀的公共前缀,M是a的长度 N是b的长度
/*
Next[i] 为 a的第i个位置开始的后缀与自身的最长公共前缀的长度
ret[i] 为b的第i个位置开始的后缀 与a的最长公共前缀的长度
*/
void ExKmp(char *a, char *b, int M,int N, int *Next,int *ret)
{
int i,j,k;
for(j=0;1+j<M&&a[j]==a[1+j];j++);
Next[1]=j;
k=1;
for(i=2;i<M;i++)
{
int Len=k+Next[k];//Next[i]对应a的第i个位置开始的后缀与自身的公共前缀
int L=Next[i-k];
if(L<Len-i) Next[i]=L;
else
{
for(j=max(0,Len-i);i+j<M && a[j]==a[i+j];j++);
Next[i]=j;
k=i;
}
}
for(j=0; j<N && j<M && a[j]==b[j] ;j++);
ret[0]=j;
k=0;
for(i=1;i<N;i++)
{
int Len=k+ret[k];
int L=Next[i-k];
if(L<Len-i) ret[i]=L;
else
{
for(j=max(0,Len-i);j<M && i+j<N && a[j]==b[i+j] ;j++);
ret[i]=j;
k=i;
}
}
}
void change(char *s,int len)
{
for(int i=0,j=len-1;i<j;i++,j--) swap(s[i],s[j]);
}
char S1[MAXN];
char S2[MAXN];
int Next[MAXN];
int ret[MAXN];
int ans[MAXN];
int main ()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(ans,0,sizeof ans);
scanf("%s",S1);
scanf("%s",S2);
int len1=(int)strlen(S1);
int len2=(int)strlen(S2);
change(S1,len1);
change(S2,len2);
ExKmp(S2,S1,len2,len1,Next,ret);
for(int i=0;i<len1;i++) ans[ret[i]]++;
LL A=0;
for(int i=len2;i;i--)
{
ans[i]+=ans[i+1];
A+=((LL)ans[i]*i)%P;
if(A>=P)A-=P;
}
printf("%d\n",(int)A);
}
return 0;
}