题面
Byteasar 想在墙上涂一段很长的字符,他为了做这件事从字符的前面一段中截取了一段作为模版. 然后将模版重复喷涂到相应的位置后就得到了他想要的字符序列.一个字符可以被喷涂很多次,但是一个位置不能喷涂不同的字符.做一个模版很费工夫,所以他想要模版的长度尽量小,求最小长度是多少.拿样例来说 ababbababbabababbabababbababbaba , 模版为前8个字符ababbaba, 喷涂的过程为: ababbababbabababbabababbababbaba
分析
仔细分析题意之后我们可以发现模板串的三条性质:
1.一个模板串A是要求的文本串B的公共前后缀
2.如果一个模板串B可以覆盖模板串A,那么B是比A更优的一个解
3.如果模板串A可以完全覆盖文本串B,那么A在B中的匹配位置(按照开头算)之间的空格数小于A的长度
由性质1可以猜想,此题大概率是用KMP求解,因为KMP正好可以求出公共前后缀长度。
因此,设next[i]表示前i位的公共前后缀长度,则序列长度只能为next[n],next[next[n]]...
将可能的长度存进数组,对于每个长度的前缀,将其和原串进行匹配,再利用性质3进行判断。
但是这样仍然会超时。
优化:从小到大枚举长度,只要有一个合法就退出。对于长度a,记录最远的匹配位置mx,若存在另
一个长度为b的模板串,且b<mx,则a可以完全覆盖b, 故b一定没有a优。由于a不可行(a在b之前被枚举,若a可行,就不会枚举b了),b一定也不可行,直接跳过
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 500005
using namespace std;
int n,m,sz;
int next[maxn];
int f[maxn];
int len[maxn];
char a[maxn],b[maxn];
int mx;
int check(int m){
for(int i=1,j=0;i<=n;i++){
while(j>0&&(j==m||b[i]!=a[j+1]) )j=next[j];
if(b[i]==a[j+1]) j++;
f[i]=j;
}
bool flag=false;
int last=1;
for(int i=1;i<=n;i++){
if(f[i]==m){
flag=true;
if(i-last-1>=m) return 0;
last=i;
mx=max(mx,i);
}
}
if(flag) return 1;
else return 0;
}
int main(){
scanf("%s",a+1);
n=strlen(a+1);
next[1]=0;
for(int i=1;i<=n;i++) b[i]=a[i];
for(int i=2,j=0;i<=n;i++){
while(j>0&&a[i]!=a[j+1]) j=next[j];
if(a[i]==a[j+1]) j++;
next[i]=j;
}
sz=0;
for(int i=n;i;i=next[i]){
len[++sz]=i;
}
int ans=n;
for(int i=sz;i>=0;i--){
if(len[i]<=mx) continue;
if(check(len[i])){
ans=len[i];
break;
}
}
printf("%d\n",ans);
}