[POI2005]SZA-Template
我们定义 $f(i)$ 表示 $[1,i]$ 的覆盖需要的最短长度。显然 $i$ 是 $f(i)$ 的最大取值。
考虑 $f(i)$ 的取值,$f(1)=1$ 显然。对于其他 $f(i)$,如果说 $[1,j]$ 可以被长度为 $x$ 的印章覆盖,且长度为 $x$ 的印章能盖出一个 $[1,i]$ 的公共前缀后缀(设长度为 $l$),并且 $l+j\ge i$,很显然我们就能通过这个长度为 $x$ 的前缀印章得到整个 $[1,i]$。
从另一个角度考虑,如果我们 KMP 后得到了 $nxt(i)$ 数组,表示 $[1,i)$ 的最长公共前缀后缀的长度,那么很显然 $f(i)$ 不是 $i$ 就是 $f(nxt(i+1))$(能覆盖 $[1,i]$ 的长度非 $i$ 的印章必然可以覆盖 $[1,nxt(i+1)]$)。
那么现在我们的问题转化为什么时候 $f(i)$ 可取 $f(nxt(i+1))$,显然根据上面的论述,只有在存在 $f(j)=f(nxt(i+1))$ 且 $j+nxt(i+1)\ge i$ 的时候可取。
具体实现中,开一个桶存储每个 $f$ 的值所对应的编号最大的 $i$ 是谁,然后需要的时候查询就行了。
时间复杂度 $O(n)$。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include<iostream> #include<cstdio> #include<cstring> #define ll long long using namespace std; namespace Ehnaev{ inline void write(ll x) { static char buf[22];static ll len=-1; if(x>=0) {do{buf[++len]=x%10+48;x/=10;}while(x);} else {putchar(45);do{buf[++len]=-(x%10)+48;x/=10;}while(x);} while(len>=0) putchar(buf[len--]); } }using Ehnaev::write;
const ll N=1e6;
ll n; ll nxt[N+5],buc[N+5],f[N+5]; char s[N+5];
int main() {
scanf("%s",s+1);n=strlen(s+1);
for(ll i=2;i<=n;i++) { ll j=nxt[i]; while(j&&s[j+1]!=s[i]) j=nxt[j+1]; j+=(s[j+1]==s[i]);nxt[i+1]=j; }
f[1]=1; for(ll i=1;i<=n;i++) { if(buc[f[nxt[i+1]]]+nxt[i+1]>=i) f[i]=f[nxt[i+1]]; else f[i]=i;buc[f[i]]=i; }
write(f[n]);
return 0; }
|