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]<<" ";
}
}
引出本题:
题意:
给一组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;
}