您的位置:首页 > 理论基础 > 计算机网络

HDU 6153 A Secret CCPC网络赛,KMP拓展应用

2017-08-22 11:37 375 查看
传送门:HDU 6153




[align=left]Sample Input[/align]

2
aaaaa
aa
abababab
aba

[align=left]Sample Output[/align]
13
19

题目大意:输入两个字符串s,t,设长度分别为ls和lt,下标从1开始,求t的所有后缀t[i..lt]在s中出现的次数乘以后缀长度的和。注意,这里的后缀是可重叠的。例如s="abababab",t="aba",后缀aba在s中出现3次,ab出现3次,a出现4次。ans=3*3+3*2+4*1=19.

前置技能:拓展KMP算法,用于求一个串的所有前缀与另一个串的最长公共前缀的长度。比如“abcabac”与“abcb”的最长公共前缀为“abc”。

思路:第一想法是直接用KMP算法求每个后缀在s中出现的次数,然后分别乘以后缀的长度并相加。但是会超时。考虑到我们做了大量的重复运算,可以把s和t字符串都翻转过来得到s'和t'。这样求t的后缀在s中出现的次数就变成了求t'的前缀在s'中出现的次数。当匹配到 i 失配时,说明前 i-1 个前缀都可以匹配,大大减少了重复运算。但是这样一来,原来的KMP算法就不再适用了。因为有些情况是匹配不到的。比如s="acab",t="ab",s'="baca",t'="ba",前缀a在s'中出现了两次,但是却只能匹配到一次。这时可以用拓展KMP算法求出s'的每个前缀和t'的最长公共前缀的长度。当s'的前缀与t'的最长公共前缀长度为n时,说明从当前位置开始有n个前缀可以匹配,他们的长度和为
1+2+…+n = (n+1)*n/2 ,只要将得到的公共前缀的长度求和就好了。

具体实现:先将两个字符串翻转,再调用一次拓展KMP算法求最长公共前缀的长度extend[i],每个最长公共前缀对结果的贡献是 extend[i]*(extend[i]-1)/2 ,相加并取模即可。

#include<stdio.h>
#include<string.h>
typedef long long LL;

int ls,lt;
int mod=1e9+7;
char s[1000010],t[1000010];
int next[1000010],extend[1000010];

void get_next()
{
int i,a=0,p=0;
next[0]=lt;
for(i=1;i<lt;i++)
{
if(i>=p || i+next[i-a]>=p)
{
if(i>=p) p=i;
while(p<lt && t[p]==t[p-i]) p++;
next[i]=p-i;
a=i;
}
else next[i]=next[i-a];
}
}

void ex_kmp()
{
int i,a=0,p=0;
get_next();
for(i=0;i<ls;i++)
{
if(i>=p || i+next[i-a]>=p)
{
if(i>=p) p=i;
while(p<ls && p-i<lt && s[p]==t[p-i])	p++;
extend[i]=p-i;
a=i;
}
else extend[i]=next[i-a];
}
}

int main()
{
int i,tt;
char c;
LL ans;
scanf("%d",&tt);
while(tt--)
{
scanf("%s%s",s,t);
ls=strlen(s);
lt=strlen(t);
for(i=0;i<ls/2;i++)
{  //将字符串s翻转
c=s[i];
s[i]=s[ls-1-i];
s[ls-1-i]=c;
}
for(i=0;i<lt/2;i++)
{  //将字符串t翻转
c=t[i];
t[i]=t[lt-1-i];
t[lt-1-i]=c;
}
ex_kmp();  //调用拓展KMP求最长公共前缀
ans=0;
for(i=0;i<ls;i++)
{ //求结果
ans+=((LL)extend[i]*(extend[i]+1)/2)%mod;
ans%=mod;
}
printf("%lld\n",ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: