前言
做這道題時,我和巨神yxc在洛咕上看到了一篇代碼奇短的題解,然後看解析,發現裏面的證明都是“顯然”、“很簡單”,被臭到。
於是zyd和yxc爆肝了2h左右,終於肝出了這個方法的證明,蜃臭。
題目鏈接:傳送門
Byteasar想在牆上塗一段很長的字符,他爲了做這件事從字符的前面一段中截取了一段作爲模版. 然後將模版重複噴塗到相應的位置後就得到了他想要的字符序列.一個字符可以被覆蓋很多次,但是一個位置不能填不同的字符.做一個模版很費工夫,所以他想要模版的長度儘量小,求最小長度是多少。
先用kmp求出字符串的next數組,然後dp。(以下next記爲nxt)
讓f[i]表示前綴爲i的模板長度的最小值。
先證明幾個引理。
註明:
用來覆蓋一個序列的模版一定是字符串的前綴,但在下文中有些地方稱爲“長度爲XXX的串”,意思是"長度爲XXX的前綴"。
引理一、f[i]的取值只能爲i或者f[nxt[i]]
引理1.1 f[i]<=i
這個很顯然。。因爲用這個字符串本身一定能覆蓋前i個字符qwq
引理1.2 f[i]>=f[nxt[i]]
如果f[i]<f[nxt[i]],那麼就無法用f[i]的長度來填充前nxt[i]個字符(否則f[nxt[i]]會更小,就矛盾了)
引理1.3 若f[i]<i,則f[i]<=nxt[i]
證明:如果f[i]>nxt[i],則長度爲f[i]時,能使此字符串的前後綴相同(如圖所示,兩段紅色的區域是相同的),因爲必須有一個長度f[i]的串在最前面,有一個長度f[i]的串在最後面,否則前面或後面會覆蓋不到qwq。
這與nxt[i]是最大的使前後綴相同的長度矛盾,所以f[i]<=nxt[i]。
引理1.4 若f[i]<nxt[i],那麼f[i]=f[nxt[i]]
證明:
當f[nxt[i]]<f[i]<nxt[i]時,覆蓋情況如圖所示。
由f[i]的定義,能用長度爲f[i]的串來覆蓋完前i個字符,且能用長度f[nxt[i]]的串覆蓋前nxt[i]個字符。
所以珂以用前f[nxt[i]]的串來覆蓋長度爲f[i]的串(因爲f[i]<nxt[i]),這與f[i]是最小的能覆蓋前i個字符的長度矛盾。
因此f[nxt[i]]<f[i]<nxt[i]不成立。
由引理1.2,f[i]>=f[nxt[i]]。由已知,f[i]<nxt[i]。
所以f[i]=f[nxt[i]]。
引理1.5 若f[i]=nxt[i],則f[nxt[i]]=nxt[i],即f[i]=f[nxt[i]]。
圖與引理1.4類似,就不畫了qwq。
因爲f[i]=nxt[i],所以珂以用長度nxt[i]的串來覆蓋前i個字符,且不能用長度比nxt[i]小的串來覆蓋前i個。
因此前nxt[i]個字符和後nxt[i]個字符之間的部分(注意這部分可能爲空,即前後nxt[i]個可能重疊)也可以用長度爲nxt[i]的前綴來覆蓋。
若f[nxt[i]]=nxt[i],由引理1.1,只可能是f[nxt[i]]<nxt[i]的情況。
因爲可以用長度爲f[nxt[i]]的串來覆蓋前nxt[i]個。
因此珂以把覆蓋前i個字符的長度爲nxt[i]的串都換成長度爲f[nxt[i]]的串。
此時f[i]應爲f[nxt[i]],而f[nxt[i]]<f[i],矛盾。
因此f[i]=f[nxt[i]]=nxt[i]。
綜上,
由引理1.1和引理1.2,有f[nxt[i]]<=f[i]<=i。
由引理1.3,有f[nxt[i]]<=f[i]<=nxt[i]或f[i]=i。
由引理1.4和引理1.5,當f[i]<=nxt[i]時,f[i]=f[nxt[i]]。
因此f[i]=f[nxt[i]]或f[i]=i。
引理二、f[i]=f[nxt[i]]的充要條件是存在j∈[i−nxt[i],i),f[j]=f[nxt[i]]
引理2.1 引理二的充分性證明,即f[i]=f[nxt[i]]=>∃j∈[i−nxt[i],i],f[j]=f[nxt[i]]
在[i−nxt[i],i]之間任意一處取一個j,發現[1,j]之間一定能被紅色線段覆蓋。
因此可以證明引理二的充分性。
引理2.2 引理二的必要性證明,即∃j∈[i−nxt[i],i],f[j]=f[nxt[i]]=>f[i]=f[nxt[i]]。
圖與引理2.1的證明類似,就不畫了qwq。
因爲∃j∈[i−nxt[i],i],f[j]=f[nxt[i]],且由f[nxt[i]]的定義,長度爲f[nxt[i]]的串能覆蓋前nxt[i]個字符,
所以(i−nxt[i],i]之間的字符也可以用f[nxt[i]]的長度的串覆蓋。
又因爲[1,j]的字符可以用f[nxt[i]]的長度的串覆蓋,且j>=i−nxt[i],
所以[1,i]的字符可以用f[nxt[i]]的長度的串覆蓋。
由引理一,f[i]=f[nxt[i]]或i。
由引理1.1,f[nxt[i]]<=nxt[i]<i,即f[nxt[i]]<i,
所以f[i]=f[nxt[i]]。
由引理2.1和引理2.2,引理二得證。
重複一次引理:
引理一、f[i]=f[nxt[i]]或i(f[nxt[i]]<i)
引理二、f[i]=f[nxt[i]]<=>存在j∈[i−nxt[i],i],f[j]=f[nxt[i]]
然後看這道題:
先kmp跑出nxt數組,然後用一個數組記錄每個f值出現的最新位置。
遍歷1~n,若f[nxt[i]]出現的最新位置>=i−nxt[i],則f[i]=f[nxt[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() {
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;
}