2017 Multi-University Training Contest - Team 4:1002. Classic Quotation(KMP+DP)




题意:给你两个字符串S和T,每次询问(L, R)求出所有满足x<=L, y>=R的(x, y),S串的前x个字符和后y个字符接在一起后包含字符串T的个数之和


说实话。。这题会做题解也还是看不懂

这题瞎搞,主要是难在求不对可能会把答案算重复

设|T| = m,|S| = n;

①对串T求next和kmp

net[q]:表示T串以第q个字符结尾的后缀能匹配的最长前缀

len[i][a]:表示如果T串的前i个字符后面接上字母a,能匹配T的最长前缀(注意不能是整个T串)

cnt[i][j]:等于1表示如果T串的前i个字符后面接上字母j,刚好后缀是T串,很显然除了cnt[m-1][T[m]]其他都为0


②搞定之后就可以对S串dp了

pri[x]:T串在S串的前i个字符中出现pri[i]次

求前缀和之前:dp[i][j]等于1表示S串以第i个字符结尾的后缀能最长匹配T串的前j个字符(当然j<m,不能满匹配)

也就是说在i相同的时候,有且只有一个j满足dp[i][j]==1,其他dp[i][]都为0

然后dp[i][j] += dp[i-1][j]


③搞定了<=L的前半部分,后半部分其实好办

在求前缀和之前:last[i][j]表示T串的前j个字符接上S串的后i个字符后包含last[i][j]个T串

然后last[i][j] += last[i+1][j]

最后对于每个询问(L, R)答案就是


#include<stdio.h>
#include<string.h>
#define LL long long
int net[105], len[105][26], cnt[105][26], pre[50005], dp[50005][105], last[50005][105];
char str[50005], jud[105];
int main(void)
{
	LL ans;
	int T, n, m, p, q, Q, i, j, x, y;
	scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d%d%s%s", &n, &m, &Q, str+1, jud+1);
		for(i=1;i<=n;i++)
			str[i] -= 'a';
		for(i=1;i<=m;i++)
			jud[i] -= 'a';
		p = 0;
		for(q=2;q<=m;q++)								//net[q]表示以第q个字符结尾的后缀能匹配的最长前缀
		{
			while(p && jud[p+1]!=jud[q])
				p = net[p];
			if(jud[p+1]==jud[q])
				p++;
			net[q] = p;
		}
		memset(cnt, 0, sizeof(cnt));
		for(i=0;i<=m-1;i++)
		{
			for(j=0;j<26;j++)
			{
				p = i;
				while(p && jud[p+1]!=j)
					p = net[p];
				if(jud[p+1]==j)
					p++;
				len[i][j] = p;						//len[i][j]表示如果T串的前i个字符后面接上字母j,能匹配T的最长前缀
			}
		}
		cnt[m-1][jud[m]] = 1;			//cnt[i][j]==1表示如果T串的前i个字符后面接上字母j,刚好后缀是T串,很显然只有1种情况
		len[m-1][jud[m]] = net[m];			//注意特判,能匹配的最长前缀不能是整个T串,很显然就是那1种情况
		p = q = 0;
		memset(dp, 0, sizeof(dp));
		for(i=1;i<=n;i++)
		{
			q += cnt[p][str[i]];
			p = len[p][str[i]];
			pre[i] = pre[i-1]+q;					//T串在S串的前i个字符中出现pri[i]次
			dp[i][p] = 1;
		}
		for(i=1;i<=n;i++)
		{
			for(j=0;j<=m-1;j++)
				dp[i][j] += dp[i-1][j];		//dp[i][j]表示T串的前j个字符作为最长前缀在S串的前i个字符中出现dp[i][j]次(当然j<m,不能满匹配),很显然这是个前缀和
		}
		/*for(i=1;i<=n;i++)
		{
			for(j=0;j<=m-1;j++)
				printf("%d ", dp[i][j]);
			printf("\n");
		}*/
		memset(last, 0, sizeof(last));
		for(i=n;i>=1;i--)
		{
			if(str[i]==jud[m])
				last[i][m-1]++;
			for(j=0;j<=m-1;j++)
				last[i][j] += last[i+1][len[j][str[i]]];					//last[i][j]表示T串的前j个字符接上S串的后i个字符能形成last[i][j]个完整的T串
		}
		for(i=n;i>=1;i--)
		{
			for(j=0;j<=m-1;j++)
				last[i][j] += last[i+1][j];						//再求后缀和
		}
		while(Q--)
		{
			scanf("%d%d", &x, &y);
			ans = (LL)(n-y+1)*pre[x];
			for(i=0;i<m;i++)
				ans += (LL)dp[x][i]*last[y][i];
			printf("%lld\n", ans);
		}
	}
	return 0;
}
/*
1
8 2 1
pppnotsb
pp
3 8
*/


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值