题解 P3426(kmp)
首先求出 next \text{next} next 数组(记为 n x t i nxt_i nxti)。
设 f i f_i fi 表示印制 [ 1 , i ] [1,i] [1,i] 所需最小印章, g i g_i gi 表示 [ 1 , i ] [1,i] [1,i] 的印章最长能印多长。
可以发现 f i f_i fi 只会有两种值: i , f n x t i i,f_{nxt_i} i,fnxti。证明如下。
- 如果 f i < f n x t i f_i<f_{nxt_i} fi<fnxti,但 [ 1 , f i ] [1,f_i] [1,fi] 必然能印制完 [ 1 , n x t i ] [1,nxt_i] [1,nxti],则 f i ≥ f n x t i f_i \geq f_{nxt_i} fi≥fnxti 矛盾。
- 如果 f i > f n x t i f_i > f_{nxt_i} fi>fnxti 且 f i ≠ i f_i \neq i fi=i,因为在同一位置上印不同字符是不允许的,所以必然 [ 1 , f i ] [1,f_i] [1,fi] 是 [ 1 , i ] [1,i] [1,i] 的后缀,也是前缀,所以可以用 f i f_i fi 更新 n x t i nxt_i nxti 的值,矛盾。
- 如果 f i > i f_i > i fi>i,显然不成立。
接下来考虑什么时候 f i f_i fi 能取到 f n x t i f_{nxt_i} fnxti:考虑 g n x t i g_{nxt_i} gnxti,若 g n x t i ≥ i − n x t i g_{nxt_i}\geq i-nxt_i gnxti≥i−nxti,则 f i f_i fi 能取到 f n x t i f_{nxt_i} fnxti。在计算完 f i f_i fi 的时候更新 g f i g_{f_i} gfi 为 i i i 即可。
//P3426
#include <cstring>
#include <cstdio>
const int N = 5e5 + 10;
int n, nxt[N], f[N], g[N];
char s[N];
int main(){
scanf("%s", s+1);
n = strlen(s+1);
for(int i = 2, j = 0; i <= n; ++ i){
while(j > 0 && s[i] != s[j+1]) j = nxt[j];
if(s[i] == s[j+1]) ++ j;
nxt[i] = j;
}
for(int i = 1; i <= n; ++ i){
if(g[f[nxt[i]]] >= i-nxt[i]) f[i] = f[nxt[i]];
else f[i] = i;
g[f[i]] = i;
}
printf("%d\n", f[n]);
return 0;
}