校赛考了个这种算法,当时不懂KMP算法于是就光荣的TLE了,赛后看题解发现是没见过的算法,写篇题解纪念一下。
KMP算法是一种能够大大降低字符串查找的时间复杂度的算法,正常来说,设被查找字符串(以下简称主串)的长度为n,需要匹配的字符串(以下简称)副串的长度为m,在最差情况下,对于0<=i<=n-m的主串里的每一个元素都需要查找m的长度,故时间复杂度为O(NM)。
暴力解法图示
那么我们怎么想办法优化呢,可以发现,我们不必要每次每次都一步步往后走,每次失败后都可以直接跳过一部分不可能成立的序列,比如上图中间的第一步可以直接跳过,故我们可以创建一个数组,数组中每个位置储存副串位置相对于头前缀的长度,即如果失败直接从副串中next对应的位置开始比较。
好了,现在问题来了,我们怎么构造这一个数组呢,考虑递推法:
- 对于除了零以外的任何元素x,如果next[0],next[1],next[2],next[3],…,next[x-1],已知,令temp=next[x-1],如果主串a[x]=a[temp],说明x是在temp的基础上的下一个元素,next[x]=temp+1;
- 如果配对失败,则说明其对应的前缀需要向前推到再上一个可能出现的位置,即temp=next[temp-1],一直到a[temp]=a[x],为止,
递推构造next数组的方法如下:其中ss为
nexttt[0]=0;
unsigned long long int x=1;
int temp=0;
while(x<ss.length())
{
if(ss[temp]==ss[x])
{
nexttt[x]=temp+1;
temp++;
x++;
}
else if(temp)
{
temp=nexttt[temp-1];
}
else
{
nexttt[x]=0;
x++;
}
}
如bbbcdd对应的next串为:0,1,2,0,0,0
然后对应的查找函数:
int tar=0;//其在主串的位置
int pos=0;//其在副串的位置
while(tar<s[k].length())
{
if(s[k][tar]==ss[pos])
{
tar++;
pos++;
}
else if(pos!=0)
{
pos=nexttt[pos-1];
}
else
{
tar++;
}
if(pos==ss.length())
{
flag=1;
}
}
最后再附上一道题:
样例:
5
3 19
iamdata juststructure notstring
datastructurestring
3 8
iiiiii llove yesxtu
ilovextu
3 3
not aurora st
nst
3 15
kids mother people
kidmotherpeople
3 6
bbbb bbbccc dddd
bbbcdd
输出结果:
4 9 6
1 4 3
1 0 2
3 6 6
0 4 2
Ac代码:
#include<iostream>
#include<string>
using namespace std;
string s[20];string s1;
int nexttt[200000];
int ans[30];
int check(string ss,int k)
{
int flag=0;
nexttt[0]=0;
unsigned long long int x=1;
int temp=0;
//cout<<"ssp= "<<ss<<endl;
while(x<ss.length())
{
//cout<<"###"<<ss[temp]<<" "<<ss[x]<<" "<<temp<<" "<<x<<endl;
if(ss[temp]==ss[x])
{
//cout<<"%%%%%"<<endl;
nexttt[x]=temp+1;
temp++;
x++;
//cout<<"XXXX= "<<x<<" "<<temp<<endl;
}
else if(temp)
{
temp=nexttt[temp-1];
}
else
{
nexttt[x]=0;
x++;
}
}
for(int i=0;i<ss.length();i++)
{
cout<<"nexttt[i]= "<<nexttt[i]<<endl;
}
int tar=0;//其在主串的位置
int pos=0;//其在副串的位置
while(tar<s[k].length())
{
if(s[k][tar]==ss[pos])
{
tar++;
pos++;
}
else if(pos!=0)
{
pos=nexttt[pos-1];
}
else
{
tar++;
}
if(pos==ss.length())
{
flag=1;
}
}
return flag;
}
void erfeng(int left,int right,int mark,int k)
{
while(left<=right)
{
int mid=(left+right)/2;
string ss;
for(int i=mark-mid;i<mark;i++)
{
ss+=s1[i];
}
//cout<<"ss= "<<ss<<endl;
int temp=check(ss,k);
if(temp==0)
{
right=mid-1;
}
else
{
ans[k]=mid;
left=mid+1;
}
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
int k,n;
cin>>k>>n;
for(int i=0;i<k;i++)
{
cin>>s[i];
}
cin>>s1;int mark=n;
for(int i=k-1;i>=0;i--)
{
int tt=s[i].length();
//cout<<"mark= "<<mark<<" "<<tt<<endl;
erfeng(0,min(tt,mark),mark,i);
mark-=ans[i];
}
for(int i=0;i<k;i++)
{
cout<<ans[i]<<" ";
}
cout<<endl;
}
}