KMP练习题集

测试地址

剪花布条

题意简述
原题来自:HDU 2087
一块花布条,里面有些图案,另有一块直接可用的小饰条,里面也有一些图案。对于给定的花布条和小饰条,计算一下能从花布条中尽可能剪出几块小饰条来呢?

解题思路
就是普通的kmp算法,只不过这里的两个模式串不能重叠,我们基于贪心策略遇到一个选一个即可。
代码示例

#include<bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
int f[N],nex[N];
char S[N],T[N];
void getNex(char *s){
	memset(nex,0,sizeof nex);
	int len = strlen(s)-1;
	for(int i = 2,j = 0;i <= len;i++){
		while(j > 0 && s[i] != s[j+1]) j = nex[j];
		if(s[i] == s[j+1]) j++;
		nex[i] = j;
	}
}
void getF(char *s,char *t){
	memset(f,0,sizeof f);
	int n = strlen(s)-1, m = strlen(t)-1;
	for(int i = 1,j = 0;i <= n;i++){
		while(j > 0 && (j == m || s[i] != t[j+1])) j = nex[j];
		if(s[i] == t[j+1]) j++;
		f[i] = j;
	}
}
void solve(){
	getNex(T);
	getF(S,T);
	int ans = 0,r = 0;
	int n = strlen(S)-1, m = strlen(T)-1;
	for(int i = 1;i <= n;i++){
		if(i > r && f[i] == m) ans++ ,r = i+m-1;
	}
	printf("%d\n",ans);
}
int main(){
	while(scanf("%s",S+1) && S[1] != '#'){
		scanf("%s",T+1); S[0] = T[0] = '#';
		solve();
	}	
	return 0;
}
Power Strings

在之前的字符串与Hash题集中做过,而且刚过去不久,不整理了。

Radio Transmission

题意简述
给你一个字符串,它是由某个字符串不断自我连接形成的。但是这个字符串是不确定的,现在只想知道它的最短长度是多少。

解题思路
求最短循环节,给定的字符串可能是由循环节循环构成的字串的一个子串,所以就直接用求最短循环节就可以了,省去用字符串hash判断该串是否满足了。
代码示例

#include<bits/stdc++.h>
const int N = 1e6+10;
char str[N];
int n,nex[N];
void getNex(char *s){
	int len = strlen(s)-1;
	for(int i = 2,j = 0;i <= len;i++){
		while(j > 0 && s[i] != s[j+1]) j = nex[j];
		if(s[i] == s[j+1]) j++;
		nex[i] = j;
	}
}
void solve(){
	getNex(str);
	printf("%d\n",n - nex[n]);
}
int main(){
	scanf("%d",&n);
	scanf("%s",str+1);
	str[0] = '#';
	solve();
	return 0;
}
OKR-Periods of Words

题意简述

原题来自:POI 2006
串是有限个小写字符的序列,特别的,一个空序列也可以是一个串。一个串 P 是串 A 的前缀,当且仅当存在串 B,使得 A=PB。如果P≠A并且 P 不是一个空串,那么我们说 P 是 A 的一个 proper 前缀。
定义 Q 是 A 的周期,当且仅当 Q 是 A 的一个 proper 前缀并且 A 是 QQ 的前缀(不一定要是 proper 前缀)。比如串 abab 和 ababab 都是串 abababa 的周期。串 A 的最大周期就是它最长的一个周期或者是一个空串(当 A 没有周期的时候),比如说,ababab 的最大周期是 abab。串 abc 的最大周期是空串。
给出一个串,求出它所有前缀的最大周期长度之和。

解题思路
题意有一些绕,但是还好给的是中文题面,尚可理解。就是说P是A的一个真子串,且是A的前缀,那么P就是A的proper前缀。若Q是A的proper前缀,且A是QQ(两个Q拼接在一起)的前缀(前缀不必要是真子串),那么Q就是A的周期。
现在要求的是A的 所有前缀的 最长周期的长度之和。

如果要是求最短周期的长度,那么直接就是求最短循环节,跑一遍nex就解决了;现在要求最长周期的长度,相当于求最长的循环节(该循环节不能等于原串!否则就不是proper前缀了),那么就不断的令 j = nex[j],找到非零的最小 nex[j] (将nex数组看作father数组,一棵father树),那么最长循环节就是 i - j 了。

这样会超时,因为我们每次令j = nex[j]相当于从叶子节点走到根节点,最坏情况是O(N)才能找到最小的非零 nex[j],总时间复杂度就是O(N^2)。

优化
我们令 f[j] 表示根到 j 路径上最小非零的值。(我们把nex数组看作一棵树!!!)
那么对于任意的nex[p],若nex[p] != 0,那么最长周期就是 p - f[nex[p] ]。
我们在求nex数组时顺便更新 f 数组,和记忆化类似,思路不难理解。

代码示例

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
const int INF = 0x3f3f3f3f;
char str[N];
int n,nex[N],f[N];

typedef long long ll;
void getNex(char *s){
	int len = strlen(s)-1; f[0] = INF;
	for(int i = 2,j = 0;i <= len;i++){
		while(j > 0 && s[i] != s[j+1]) j = nex[j];
		if(s[i] == s[j+1]) j++;
		nex[i] = j;
		if(j) f[j] = min(j,f[nex[j]]);
	//	printf("%d %d\n",j,f[j]);
	}
}
void solve(){
	getNex(str);
	long long ans = 0;
	for(int i = 1;i <= n;i++){
		if(nex[i]) ans += i - f[nex[i]];
		//printf("%d %c\n",ans,str[i]);
		//printf("%d %d\n",nex[i],f[i]);
	}
	printf("%lld\n",ans);
}
int main(){
	//freopen("123.txt","r",stdin);
	scanf("%d",&n);
	scanf("%s",str+1);
	str[0] = '#';
	solve();
	return 0;
}
似乎在梦中见过的样子

题意简述
原题来自:2014 年湖北省队互测 Week2
「Madoka,不要相信 QB!」伴随着 Homura 的失望地喊叫,Madoka 与 QB 签订了契约。
这是 Modoka 的一个噩梦,也同时是上个轮回中所发生的事。为了使这一次 Madoka 不再与 QB 签订契约,Homura 决定在刚到学校的第一天就解决 QB。然而,QB 也是有许多替身的(但在第八话中的剧情显示它也有可能是无限重生的),不过,意志坚定的 Homura 是不会放弃的——她决定消灭所有可能是 QB 的东西。现在,她已感受到附近的状态,并且把它转化为一个长度为 n 的字符串交给了学 OI 的你。
现在你从她的话中知道,所有形似于 A+B+A 的字串都是 QB 或它的替身,且 ∣A∣≥k,∣B∣≥1(位置不同其他性质相同的子串算不同子串,位置相同但拆分不同的子串算同一子串),然后你必须尽快告诉 Homura 这个答案——QB 以及它的替身的数量。
注:对于一个字符串 S,∣S∣ 表示 S 的长度。

解题思路
这题需要对照样例来理解题意,经过分析后,发现题意是如果有一个子串满足ABA形式,其中 |A|>=k 且 B 不是空串,那么该子串就是QB大魔王。现在问有多少个这样的子串。

首先看思路,对于一个字符串(s[1,n]),我们通过求它的nex数组,如果nex[n] >= k 并且 nex[n] 小于 n/2,那么该串就是QB;如果nex[n] >= n/2,那么nex[ nex[n] ] ,一直到根节点的路径上,只要有一个nex大于k且小于n/2,该串就也是QB。

综上所述,我们对于一个下标为[1, p]的字符串,需要找到nex[p]到根节点(将nex数组看作一棵树)路径上大于等于 k 的最小 nex值(贪心思想)。我们用 f 数组来存放该值。之所以用 f 数组存放,是为了防止最坏情况O(N^2)遍历。

所以我们对原字符串 S 的每一个后缀都进行上述操作,时间复杂度共O(N^2),勉强过。但是常数稍微大一些就过不了了,所以统计答案也要在求 nex 和更新 f 数组时顺便累加,如果觉得这样太怂了,还可以进行别的优化,不过我懒得改了。

代码示例

#include<bits/stdc++.h>
using namespace std;
const int N = 15000+10;
const int INF = 0x3f3f3f3f;
char str[N];
int nex[N],k, f[N], ans = 0;
//f[i]为nex[i]到根中,大于k的最小的一个 
void getNex(char *s){
	int len = strlen(s)-1;
	memset(nex,0,sizeof nex); f[0] = INF;
	for(int i = 2,j = 0;i <= len;i++){
		while(j > 0 && s[i] != s[j+1]) j = nex[j];
		if(s[i] == s[j+1]) j++;
		nex[i] = j;
		if(j < k) f[j] = INF; 
		else f[j] = min(j,f[nex[j]]);
		if(f[j]<<1 < i) ans++;
		//printf("%d %d %d %d\n",i,j,f[j],nex[j]);
	}
}
void solve(){
	int n = strlen(str)-1;
	for(int i = 1;i < n;i++){
		getNex(str+i-1);
	}	
	printf("%d\n",ans);
}
int main(){
	//freopen("123.txt","r",stdin);
	scanf("%s",str+1);
	scanf("%d",&k);
	str[0] = '#';
	solve();
	return 0;
}
Censoring

这个我一看就知道KMP很难写,应该用自动机,所以放在自动机专题整理,另外吐槽一下书上的翻译是谁写的,太敷衍了吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷亭1213

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值