题意,给定一个字符串S,用另一个字符串T, 以 T的前缀1 + T的前缀2 + …… + T 的形式来得到S,求T的最小长度。
集训队比赛的时候,我们想到的是枚举后缀,然后匹配,因为这样保证了后缀一定满足,但这样的话,长度必须一个个向上加,而且每次都要遍历一遍字符串,果断TLE了。
看了题解才知道,枚举前缀加上贪心的思想可以更快,而且做倒O(n)的复杂度。
首先我们把模式串p定为s[0],然后进行匹配,当匹配过程中出现失配(设失配的字符为Z),并且一直失配到头指针且和头字符不相等的时候,我们就把 文本串中从上一次完美匹配的位置到当前位置的所有字符 全部加入到p后。(为什么可以这样,因为Z是p中一个未出现过的字符,所以Z不可能作为一个前缀字符,并且只能作为最后一个字符,而当它作为最后一个字符的时候,前面的那些非完美匹配的字符就只能跟在它前面了。)
至于要满足后缀,只需要把文本串中从 最后一次完美匹配的位置到文本末尾 所有的字符加入p后就可以了。
匹配过程复杂度为O(n),next函数由于可以边加边算,所以复杂度上限也就是 O(n),甚至文本串的长度都可以不用遍历一遍求出,所以总的复杂度不会超过O(2n)
贴代码。
#include<cstring>
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 100005
int next[MAXN];
char s[MAXN],p[MAXN];
void getnext(int n,int st)
{
for(int i=st+1;i<n;i++)
{
int j=next[i];
while(j&&p[i]!=p[j])
{
j=next[j];
}
if(p[i]==p[j])
{
next[i+1]=j+1;
}
else
{
next[i+1]=0;
}
}
}
int ans;
int updata(int last,int now,int st)
{
// int len=strlen(p) //因为加了这句,TLE调了我好久。
int len=st+1;
for(int i=last+1;i<=now;i++)
{
p[len++]=s[i];
}
p[len]='\0';
getnext(len,st);
return len;
}
int kmp(int m)
{
getnext(m,0);
int j=0;
int last;
for(int i=0;s[i];i++)
{
while(j&&s[i]!=p[j])
j=next[j];
if(j==0&&s[i]!=p[j])
{
m=updata(last,i,m-1);
last=i;
}
if(s[i]==p[j])
j++;
if(j==m)
{
last=i;
}
}
for(int i=last+1;s[i];i++)
m++;
ans=m;
return -1;
}
int main()
{
int cas=1;
while(scanf("%s",&s)!=EOF)
{
memset(next,0,sizeof(next));
p[0]=s[0];
p[1]='\0';
ans=1;
kmp(1);
printf("Case %d: %d\n",cas++,ans);
}
}