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

传送门:HDU 6153


Sample Input
 
 
2aaaaaaaabababababa
Sample Output
 
1319

题目大意

输入两个字符串s,t,设长度分别为ls和lt,下标从1开始,求t的所有后缀t[i..lt]在s中出现的次数乘以后缀长度的和。注意,这里的后缀是可重叠的。例如s="abababab",t="aba",后缀aba在s中出现3次,ba出现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;
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值