【洛谷】P4391 [BOI2009]Radio Transmission 无线传输 && P3375 【模板】KMP字符串匹配

26 篇文章 0 订阅

首先来看洛谷的P4391,题目链接https://www.luogu.org/problemnew/show/P4391

想了几分钟我是没什么思路的,只想到了hash,但感觉不是正解(看题解还真的有人用hash做出来了orz),然后就打开题解了,发现这题解法的确妙,这也是写下这篇博客记录的原因。

 

本题算法思路需要有kmp算法基础才能看懂。

这里推荐与记录两篇好的博客和两期视频:

https://www.cnblogs.com/yjiyjige/p/3263858.html

https://www.cnblogs.com/tangzhengyue/p/4315393.html

https://www.bilibili.com/video/av11866460?from=search&seid=8856187916352791722

https://www.bilibili.com/video/av16828557?from=search&seid=8856187916352791722

 

思路是怎么样的呢,例如abc,我们很明显肉眼就能看出是由abc三个字符剪切一次而成,

abcabc,我们肉眼很明显能看出是abc三个字符剪切两次而成,因为他前缀abc和后缀abc相等,

abcabcabc,我们肉眼很明显能看出是由abc剪切三次而成,因为有前中后缀abc,但就是在这里我们可以总结规律了,把中缀加入前后缀来思考,只用前后缀,那他们的最长相同前后缀是多少呢?abcabc,6位,这6位后缀同时也是最前面一份复制出来的,哪怕是abcdabcdabcdabcd这种,最长相同前后缀abcdabcdabcd也是由第一份复制出来的,换句话来说,我们只要用总的长度把这个最长相同前后缀减去就是答案要的原始字符串长度了(但是看作是减去这个后缀得到的前缀)。

 

这里是不少头少尾的特殊情况,那么看看一般情况呢?

例如题目中说明的由abc自我复制而成的cabcabca,也就是ab[cabcabca]bc,

这里我们可以不把他思考为abc自我复制而成的,思考成cab自我复制而成的(这里只是为了方便思考),也就是[cabcabca]b,这样的话就不是少头少尾了,只是少尾,那我们用总的长度减去最长相同前后缀也就是cabca,只剩前缀cab,我们也是可以看成是由cab复制3份而成的。

总结来说就是我们可以先找到最长相同前后缀,把他看作后缀重复部分,用总长减去这个重复部分,剩下的就是答案了。

那么这个最长相同前后缀要怎么求呢???这在上面的kmp算法的求next数组就是了,也就是答案是next[n]-n(注意cpp11中std:next是有函数的,而且算法竞赛中经常会使用std命名空间,所以不要取名next),下面附上我这题的AC代码

#include<iostream>
#include<cstdio>
using namespace std;
char str[1000005];
int nxt[1000005];
int j=0,k;
int main()
{
	int n;
	scanf("%d\n%s",&n,str);
	nxt[0]=k=-1;
	while(j<n)
	{
		if(k==-1||str[j]==str[k])
			nxt[++j]=++k;
		else
			k=nxt[k];
	}
	printf("%d",n-nxt[n]);
	return 0;
}

 

然后本篇都讲了kmp了就再记录一下我的kmp模板,就用洛谷p3375的模板题

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char T[1000005],P[10000005];
int nxt[1000005],lent,lenp;
void get_next()
{
	int j=0,k=-1;
	nxt[0]=-1;
	while(j<lenp)
	{
		if(k==-1||P[k]==P[j])
			nxt[++j]=++k;
		else
			k=nxt[k];
	}
}
void kmp()
{
	int i=0,j=0;
	while(i<lent)
	{
		if(j==-1||T[i]==P[j])
		{
			i++;
			j++;
		}
		else
		{
			j=nxt[j];
		}
		
		if(j==lenp)
			printf("%d\n",i-lenp+1);
	}
}
int main()
{
	scanf("%s\n%s",T,P);
	lenp=strlen(P);
	lent=strlen(T);
	get_next();
	kmp();
	for(int i=1;i<=lenp;i++)
		printf("%d ",nxt[i]);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值