前言
做这道题时,我和巨神yxc在洛咕上看到了一篇代码奇短的题解,然后看解析,发现里面的证明都是“显然”、“很简单”,被臭到。
于是zyd和yxc爆肝了2h左右,终于肝出了这个方法的证明,蜃臭。
题目链接:传送门
Byteasar想在墙上涂一段很长的字符,他为了做这件事从字符的前面一段中截取了一段作为模版. 然后将模版重复喷涂到相应的位置后就得到了他想要的字符序列.一个字符可以被覆盖很多次,但是一个位置不能填不同的字符.做一个模版很费工夫,所以他想要模版的长度尽量小,求最小长度是多少。
先用kmp求出字符串的
n
e
x
t
next
next数组,然后dp。(以下
n
e
x
t
next
next记为
n
x
t
nxt
nxt)
让
f
[
i
]
f[i]
f[i]表示前缀为
i
i
i的模板长度的最小值。
先证明几个引理。
注明:
用来覆盖一个序列的模版一定是字符串的前缀,但在下文中有些地方称为“长度为XXX的串”,意思是"长度为XXX的前缀"。
引理一、 f [ i ] f[i] f[i]的取值只能为 i i i或者 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]
引理1.1
f
[
i
]
<
=
i
f[i]<=i
f[i]<=i
这个很显然。。因为用这个字符串本身一定能覆盖前
i
i
i个字符qwq
引理1.2
f
[
i
]
>
=
f
[
n
x
t
[
i
]
]
f[i]>=f[nxt[i]]
f[i]>=f[nxt[i]]
如果
f
[
i
]
<
f
[
n
x
t
[
i
]
]
f[i]<f[nxt[i]]
f[i]<f[nxt[i]],那么就无法用
f
[
i
]
f[i]
f[i]的长度来填充前
n
x
t
[
i
]
nxt[i]
nxt[i]个字符(否则
f
[
n
x
t
[
i
]
]
f[nxt[i]]
f[nxt[i]]会更小,就矛盾了)
引理1.3 若
f
[
i
]
<
i
f[i]<i
f[i]<i,则
f
[
i
]
<
=
n
x
t
[
i
]
f[i]<=nxt[i]
f[i]<=nxt[i]
证明:如果
f
[
i
]
>
n
x
t
[
i
]
f[i]>nxt[i]
f[i]>nxt[i],则长度为
f
[
i
]
f[i]
f[i]时,能使此字符串的前后缀相同(如图所示,两段红色的区域是相同的),因为必须有一个长度
f
[
i
]
f[i]
f[i]的串在最前面,有一个长度
f
[
i
]
f[i]
f[i]的串在最后面,否则前面或后面会覆盖不到qwq。
这与
n
x
t
[
i
]
nxt[i]
nxt[i]是最大的使前后缀相同的长度矛盾,所以
f
[
i
]
<
=
n
x
t
[
i
]
f[i]<=nxt[i]
f[i]<=nxt[i]。
引理1.4 若
f
[
i
]
<
n
x
t
[
i
]
f[i]<nxt[i]
f[i]<nxt[i],那么
f
[
i
]
=
f
[
n
x
t
[
i
]
]
f[i]=f[nxt[i]]
f[i]=f[nxt[i]]
证明:
当
f
[
n
x
t
[
i
]
]
<
f
[
i
]
<
n
x
t
[
i
]
f[nxt[i]]<f[i]<nxt[i]
f[nxt[i]]<f[i]<nxt[i]时,覆盖情况如图所示。
由
f
[
i
]
f[i]
f[i]的定义,能用长度为
f
[
i
]
f[i]
f[i]的串来覆盖完前
i
i
i个字符,且能用长度
f
[
n
x
t
[
i
]
]
f[nxt[i]]
f[nxt[i]]的串覆盖前
n
x
t
[
i
]
nxt[i]
nxt[i]个字符。
所以珂以用前
f
[
n
x
t
[
i
]
]
f[nxt[i]]
f[nxt[i]]的串来覆盖长度为
f
[
i
]
f[i]
f[i]的串(因为
f
[
i
]
<
n
x
t
[
i
]
f[i]<nxt[i]
f[i]<nxt[i]),这与
f
[
i
]
f[i]
f[i]是最小的能覆盖前
i
i
i个字符的长度矛盾。
因此
f
[
n
x
t
[
i
]
]
<
f
[
i
]
<
n
x
t
[
i
]
f[nxt[i]]<f[i]<nxt[i]
f[nxt[i]]<f[i]<nxt[i]不成立。
由引理1.2,
f
[
i
]
>
=
f
[
n
x
t
[
i
]
]
f[i]>=f[nxt[i]]
f[i]>=f[nxt[i]]。由已知,
f
[
i
]
<
n
x
t
[
i
]
f[i]<nxt[i]
f[i]<nxt[i]。
所以
f
[
i
]
=
f
[
n
x
t
[
i
]
]
f[i]=f[nxt[i]]
f[i]=f[nxt[i]]。
引理1.5 若
f
[
i
]
=
n
x
t
[
i
]
f[i]=nxt[i]
f[i]=nxt[i],则
f
[
n
x
t
[
i
]
]
=
n
x
t
[
i
]
f[nxt[i]]=nxt[i]
f[nxt[i]]=nxt[i],即
f
[
i
]
=
f
[
n
x
t
[
i
]
]
f[i]=f[nxt[i]]
f[i]=f[nxt[i]]。
图与引理1.4类似,就不画了qwq。
因为
f
[
i
]
=
n
x
t
[
i
]
f[i]=nxt[i]
f[i]=nxt[i],所以珂以用长度
n
x
t
[
i
]
nxt[i]
nxt[i]的串来覆盖前
i
i
i个字符,且不能用长度比
n
x
t
[
i
]
nxt[i]
nxt[i]小的串来覆盖前
i
i
i个。
因此前
n
x
t
[
i
]
nxt[i]
nxt[i]个字符和后
n
x
t
[
i
]
nxt[i]
nxt[i]个字符之间的部分(注意这部分可能为空,即前后
n
x
t
[
i
]
nxt[i]
nxt[i]个可能重叠)也可以用长度为
n
x
t
[
i
]
nxt[i]
nxt[i]的前缀来覆盖。
若
f
[
n
x
t
[
i
]
]
≠
n
x
t
[
i
]
f[nxt[i]] \not=nxt[i]
f[nxt[i]]=nxt[i],由引理1.1,只可能是
f
[
n
x
t
[
i
]
]
<
n
x
t
[
i
]
f[nxt[i]]<nxt[i]
f[nxt[i]]<nxt[i]的情况。
因为可以用长度为
f
[
n
x
t
[
i
]
]
f[nxt[i]]
f[nxt[i]]的串来覆盖前
n
x
t
[
i
]
nxt[i]
nxt[i]个。
因此珂以把覆盖前
i
i
i个字符的长度为
n
x
t
[
i
]
nxt[i]
nxt[i]的串都换成长度为
f
[
n
x
t
[
i
]
]
f[nxt[i]]
f[nxt[i]]的串。
此时
f
[
i
]
f[i]
f[i]应为
f
[
n
x
t
[
i
]
]
f[nxt[i]]
f[nxt[i]],而
f
[
n
x
t
[
i
]
]
<
f
[
i
]
f[nxt[i]]<f[i]
f[nxt[i]]<f[i],矛盾。
因此
f
[
i
]
=
f
[
n
x
t
[
i
]
]
=
n
x
t
[
i
]
f[i]=f[nxt[i]]=nxt[i]
f[i]=f[nxt[i]]=nxt[i]。
综上,
由引理1.1和引理1.2,有
f
[
n
x
t
[
i
]
]
<
=
f
[
i
]
<
=
i
f[nxt[i]]<=f[i]<=i
f[nxt[i]]<=f[i]<=i。
由引理1.3,有
f
[
n
x
t
[
i
]
]
<
=
f
[
i
]
<
=
n
x
t
[
i
]
f[nxt[i]]<=f[i]<=nxt[i]
f[nxt[i]]<=f[i]<=nxt[i]或
f
[
i
]
=
i
f[i]=i
f[i]=i。
由引理1.4和引理1.5,当
f
[
i
]
<
=
n
x
t
[
i
]
f[i]<=nxt[i]
f[i]<=nxt[i]时,
f
[
i
]
=
f
[
n
x
t
[
i
]
]
f[i]=f[nxt[i]]
f[i]=f[nxt[i]]。
因此
f
[
i
]
=
f
[
n
x
t
[
i
]
]
f[i]=f[nxt[i]]
f[i]=f[nxt[i]]或
f
[
i
]
=
i
f[i]=i
f[i]=i。
引理二、 f [ i ] = f [ n x t [ i ] ] f[i]=f[nxt[i]] f[i]=f[nxt[i]]的充要条件是存在 j ∈ [ i − n x t [ i ] , i ) j \in [i-nxt[i],i) j∈[i−nxt[i],i), f [ j ] = f [ n x t [ i ] ] f[j]=f[nxt[i]] f[j]=f[nxt[i]]
引理2.1 引理二的充分性证明,即
f
[
i
]
=
f
[
n
x
t
[
i
]
]
=
>
∃
j
∈
[
i
−
n
x
t
[
i
]
,
i
]
,
f
[
j
]
=
f
[
n
x
t
[
i
]
]
f[i]=f[nxt[i]] => \exist j\in[i-nxt[i],i],f[j]=f[nxt[i]]
f[i]=f[nxt[i]]=>∃j∈[i−nxt[i],i],f[j]=f[nxt[i]]
在
[
i
−
n
x
t
[
i
]
,
i
]
[i-nxt[i],i]
[i−nxt[i],i]之间任意一处取一个
j
j
j,发现
[
1
,
j
]
[1,j]
[1,j]之间一定能被红色线段覆盖。
因此可以证明引理二的充分性。
引理2.2 引理二的必要性证明,即
∃
j
∈
[
i
−
n
x
t
[
i
]
,
i
]
,
f
[
j
]
=
f
[
n
x
t
[
i
]
]
=
>
f
[
i
]
=
f
[
n
x
t
[
i
]
]
\exist j\in[i-nxt[i],i],f[j]=f[nxt[i]]=>f[i]=f[nxt[i]]
∃j∈[i−nxt[i],i],f[j]=f[nxt[i]]=>f[i]=f[nxt[i]]。
图与引理2.1的证明类似,就不画了qwq。
因为
∃
j
∈
[
i
−
n
x
t
[
i
]
,
i
]
,
f
[
j
]
=
f
[
n
x
t
[
i
]
]
\exist j\in[i-nxt[i],i],f[j]=f[nxt[i]]
∃j∈[i−nxt[i],i],f[j]=f[nxt[i]],且由
f
[
n
x
t
[
i
]
]
f[nxt[i]]
f[nxt[i]]的定义,长度为
f
[
n
x
t
[
i
]
]
f[nxt[i]]
f[nxt[i]]的串能覆盖前
n
x
t
[
i
]
nxt[i]
nxt[i]个字符,
所以
(
i
−
n
x
t
[
i
]
,
i
]
(i-nxt[i],i]
(i−nxt[i],i]之间的字符也可以用
f
[
n
x
t
[
i
]
]
f[nxt[i]]
f[nxt[i]]的长度的串覆盖。
又因为
[
1
,
j
]
[1,j]
[1,j]的字符可以用
f
[
n
x
t
[
i
]
]
f[nxt[i]]
f[nxt[i]]的长度的串覆盖,且
j
>
=
i
−
n
x
t
[
i
]
j>=i-nxt[i]
j>=i−nxt[i],
所以
[
1
,
i
]
[1,i]
[1,i]的字符可以用
f
[
n
x
t
[
i
]
]
f[nxt[i]]
f[nxt[i]]的长度的串覆盖。
由引理一,
f
[
i
]
=
f
[
n
x
t
[
i
]
]
f[i]=f[nxt[i]]
f[i]=f[nxt[i]]或
i
i
i。
由引理1.1,
f
[
n
x
t
[
i
]
]
<
=
n
x
t
[
i
]
<
i
f[nxt[i]]<=nxt[i]<i
f[nxt[i]]<=nxt[i]<i,即
f
[
n
x
t
[
i
]
]
<
i
f[nxt[i]]<i
f[nxt[i]]<i,
所以
f
[
i
]
=
f
[
n
x
t
[
i
]
]
f[i]=f[nxt[i]]
f[i]=f[nxt[i]]。
由引理2.1和引理2.2,引理二得证。
重复一次引理:
引理一、
f
[
i
]
=
f
[
n
x
t
[
i
]
]
或
i
f[i]=f[nxt[i]]或i
f[i]=f[nxt[i]]或i(
f
[
n
x
t
[
i
]
]
<
i
f[nxt[i]]<i
f[nxt[i]]<i)
引理二、
f
[
i
]
=
f
[
n
x
t
[
i
]
]
<
=
>
存
在
j
∈
[
i
−
n
x
t
[
i
]
,
i
]
,
f
[
j
]
=
f
[
n
x
t
[
i
]
]
f[i]=f[nxt[i]]<=>存在j\in[i-nxt[i],i],f[j]=f[nxt[i]]
f[i]=f[nxt[i]]<=>存在j∈[i−nxt[i],i],f[j]=f[nxt[i]]
然后看这道题:
先kmp跑出
n
x
t
nxt
nxt数组,然后用一个数组记录每个
f
f
f值出现的最新位置。
遍历1~n,若
f
[
n
x
t
[
i
]
]
f[nxt[i]]
f[nxt[i]]出现的最新位置
>
=
i
−
n
x
t
[
i
]
>=i-nxt[i]
>=i−nxt[i],则
f
[
i
]
=
f
[
n
x
t
[
i
]
]
f[i]=f[nxt[i]]
f[i]=f[nxt[i]],否则为
i
i
i。
毒瘤代码
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#define re register int
using namespace std;
typedef long long ll;
int read() {
re x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=10*x+ch-'0';
ch=getchar();
}
return x*f;
}
const int Size=500005;
int n,b[Size],f[Size],nxt[Size];
char str[Size];
int main() {
// n=read();
scanf("%s",str+1);
n=strlen(str+1);
int j=0;
for(re i=2; i<=n; i++) {
while(j>0 && str[j+1]!=str[i]) j=nxt[j];
if(str[j+1]==str[i]) j++;
nxt[i]=j;
}
for(re i=2; i<=n; i++) {
f[i]=i;
if(b[f[nxt[i]]]>=i-nxt[i]) {
f[i]=f[nxt[i]];
}
b[f[i]]=i;
}
printf("%d",f[n]);
return 0;
}