题-Slot Machines(KMP, next)

Slot Machines

传送门

什么是KMP?

1.现在我们面临这样一个问题:有一个文本串s和一个模式串p,现在要查找p在s中的位置。
朴素算法:o( n 2 n^2 n2);
用KMP算法实现(对next未优化) o(k*n);

在朴素算法中:当s[i]!=a[j]; 直接从i-j的位置开始.这样就很麻烦很重复。
引入next数组,存放前缀和后缀的最大长度。

怎么求next的数组呢??

两种写法:
1.next[i]表示 0~i-1的串中前后缀相同的最大长度。 (初始下标为0)
2.next[i]表示1~i的串中前后缀相同的最大长度。(初始下标为1)
ps:怎么初始都是为了方便.写两种next写法也是为了方便。

next的第一种写法

博主是用dp的思想来实现的。
具体是三步走:

  • 初始化
  • 寻找dp的方程
  • 得出结果

这里需要注意一下next数组的细节,比如下面的next[6]=2; 含义是ABCDAB的前后缀相同的最大长度,不是ABCDABD!!!
ps:我这里的下标是从0开始~。
而dp方程也很好实现: 在求next[i] (i>=1)时,需要用到next[i-1]
那么就判断:a[i]==a[next[i-1]+1] 可以,即是next[i]=next[i-1]+1;
不行即是:下一个next的咯,一直套娃。
在这里插入图片描述

next的用处一:求模式串在原串的位置。

代码如下:

#include<iostream>
#include<algorithm>
#include<math.h>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e6+10;
const ll mod=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
char s[N],a[N]; 
int net[N];
int solve(){
	cin>>s>>a;
	int lens=strlen(s);
	int lenp=strlen(a);
	rep(i,0,lenp-1){
		if(i==0){ 
			net[i]=-1;
		}else if(i==1){
			net[i]=0;
		}else{
			//j 即是要判断的点也是反应有多少点。 
			//指的也是下标 
			int j=net[i-1];
			while(j!=-1){
				if(a[j]==a[i-1]) break;
				j=net[j];
			}
			net[i]=j+1;
		}
	}
	for(int i=0;i<lenp;i++){
		cout<<net[i]<<" ";
	}
	cout<<endl;
	int i=0,j=0,k=-1;
	while(i<lens&&j<lenp){
		if(j==-1||s[i]==a[j]){
			i++;
			j++;
		}else{
			j=net[j];
		}
		if(j==lenp) return i-j;
	}
	return -1;
}
int main (){
    cout<<solve();
    getchar();
    getchar();
    return 0;
}

next数组的第二两种写法

  • 实现前缀后缀最大公共元素的长度:

各个前缀后缀的最大公共元素长度?怎么求呢? 比如下图。
发现特点:前缀后缀的最大公共元素长度右移动一个单位并将初始值赋为-1,就是next数组。
在这里插入图片描述
在这里插入图片描述

那第二种next的用途是什么呢???

用途:
找一串或一组数组的最小循环节,即最小周期是:T=next[i]-i;
eg: ABABABABA (这里为了方便,数组初始下标为1)
对应的next数组: 0 0 1 2 3 4 5 6 7 (数组初始下标为1)
拿next[9]=7 举例:
如图:前后缀串:在这里插入图片描述
前缀串有①可以确定④是AB,由④确定②是AB…同理可得。。。。
前9个数的循环周期是2。
刚好就是 T=9-next[9]
所以:T=i-next[i];

代码如下: 求next数组: 这里的模式串 初始是下标是1 。 (方便为主,看个人!)

char a[N];
int next[N];
int solve(){
	//这里的模式串 初始是下标是1  !!!
	cin>>a+1;
	int lenp=strlen(a+1);
	//cout<<lenp<<endl;
	rep(i,1,lenp){
		if(i==1){
			net[i]=0;
	   }
		else{
			//net[i-1] 即是前缀串的最后一个位置, +1 判断和a[i] 的是否等于。 
			 int j=net[i-1]+1;
			 while(j!=1){
			 	if(a[j]==a[i]) break;
				 //后面是一直套娃。 
			 	j=net[j-1]+1;
			 }
			 //这里特判是因为:是从while条件语句跳出, break语句跳出就执行if语句. 
			  if(a[j]==a[i]) net[i]=j;
			  else 
			  net[i]=0;
		}
	}
	for(int i=1;i<=lenp;i++){
		cout<<net[i]<<" ";
	}
}

引出本题:

Slot Machines 传送门

题意:

给一组n个(n<=1e6)数字,找出k+p的最小值。
p:这组数组的循环节的最小周期。
k:原数组个数去掉循环的数组后剩下的数字的个数。
k是原数组前k个数,不是任意的位置去掉k个!!!

思路:

从后面往前面找咯,主要是用到KMP算法的next数组即可。
但要从后往前面找。

代码如下:

#include<iostream>
#include<algorithm>
#include<math.h>
#include<cstring>
#include<vector>
#include<queue>
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
const int N=1e6+10;
const ll mod=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
int a[N],net[N];
int n;
void solve(){
	n=read();
	rep(i,1,n) a[i]=read();
	//反串 
	for(int i=1;i+i<=n;i++) swap(a[i],a[n-i+1]);
	//nxt[1]=0;
	//得出第二种next写法的数组。 
	rep(i,1,n){
		if(i==1){
			net[i]=0;
		}else if(i==2){
		    if(a[1]==a[2]) net[i]=1;
		}else {
			int j=net[i-1]+1;
			while(j!=1){
				if(a[j]==a[i]) break;
				j=net[j-1]+1;
			}
			if(a[i]==a[j]) net[i]=j;
			else net[i]=0;
			//net[i]=j;
		}
	}
	//for(int i=1;i<=n;i++) cout<<net[i]<<" "; cout<<endl;
	int k=1e6+10,p=1e6+10;
	rep(i,1,n){
		//根据T=next[i]-i
		//p=T=i-net[i]  k=n-i  所以这里的if语句合并了成 k+p=n-net[i] 
		if(n-net[i]<k+p){
			p=i-net[i];
			k=n-i;
		}
	}
	cout<<k<<" "<<p;
	return ;
}
int main (){
	solve();
	return 0;
}

参考资料

v_JULY_v:从头到尾彻底理解KMP(2014年8月22日版)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

axtices

谢谢您的打赏

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

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

打赏作者

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

抵扣说明:

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

余额充值