KMP & 二分 解决 P3426 SZA-Template

· · 题解

KMP & 二分 解决 P3426 SZA-Template

前言

最近学二分学魔怔了,看到

## 观察 首先,题目要求“**印章**”(即**模式串**)必须能**刚好盖完文本**(即**主串**)。 我们很容易了解到,**模式串**必须是**主串**的**前后缀**。 于是我就联想到了 **KMP** 中的 **Border**。 ## 算法运用 - KMP 很明显,由于**模式串**必须是**主串**的**前后缀**,所以**模式串**可以是**主串**(最坏情况),也可以是**主串**的 **Border** ,还可以是**主串**的 **Border** 的 **Border**……~~(晕)~~ 以此类推,我们可以得到**不大于** $n$ **个**的**可能的模式串**。 如下图 ```cpp ababbababbabababbabababbababbaba| ababbababbaba|bab... ababbaba|bba... aba|bba... a|bab... ``` 我们利用 **KMP** 中的 **next** 数组将这些**可能的模式串**求出并存储。 *Code*: ```cpp for(int i=2,j=0;i<=n;i++) { while(a[i]!=a[j+1]&&j)j=ne[j]; j+=(a[i]==a[j+1]),ne[i]=j; } for(int i=n;i;i=ne[i]) num[++sum]=i; ``` ## 暴力 - 85分 那么,我们就有一个简单的思路,**从小到大**依次**枚举**每一个**可能的模式串**,$\operatorname{check}$ 是否能完全覆盖主串,此时**第一个**满足的就是答案。 *Code*: ```cpp #include<iostream> #define N 500005 using namespace std; string a; int n; int f,last; int num[N],sum; int ne[N]; bool check(int x) { last=0; for(int i=1,j=0;i<=n;i++) { if(i-last>x) return 0; while(a[i]!=a[j+1]&&j)j=ne[j]; j+=(a[i]==a[j+1]); j==x?(last=i,j=ne[j]):0; } return 1; } int main() { cin>>a; n=a.size(); a=" "+a; for(int i=2,j=0;i<=n;i++) { while(a[i]!=a[j+1]&&j)j=ne[j]; j+=(a[i]==a[j+1]),ne[i]=j; } for(int i=n;i;i=ne[i]) num[++sum]=i; for(int i=sum;i>=1;i--) if(check(num[i])) { cout<<num[i]; return 0; } } ``` 在**最坏情况**下,这个算法的**时间复杂度**为 $O(n^2)$,而题目条件给出的 $n$ 的**数据范围**为 $n\le5*10^5$,所以会 **TLE**。 ## 优化 - 二分 我们观察每个**可能模式串**之间的**联系**,容易发现:**如果主串的** $n$ **次** **Border** **无法通过** $\operatorname{check}$,**那么主串的** $n+1$ **次** **Border** **也无法通过** $\operatorname{check}$。 也就是说,**可能模式串** $\operatorname{check}$ 的结果**满足单调性**。 同时,$n\le5\times10^5$ 的**数据范围**符合 $O(n\log{n})$ 的**时间复杂度**。 我们就可以愉快的~~麻木的~~**二分**了。 最后加入一些**最坏情况**(最优模式串为主串)的特判就能通过了。 *Code*: ```cpp #include<iostream> #define N 500005 using namespace std; string a; int n; int f,last; int num[N],sum; int ne[N]; bool check(int x) { last=0; for(int i=1,j=0;i<=n;i++) { if(i-last>x) return 0; while(a[i]!=a[j+1]&&j)j=ne[j]; j+=(a[i]==a[j+1]); j==x?(last=i,j=ne[j]):0; } return 1; } int find() { int l=1,r=sum,mid; while(l<r) { mid=l+r+1>>1; if(check(num[mid]))l=mid; else r=mid-1; } return l; } int main() { cin>>a; n=a.size(); a=" "+a; for(int i=2,j=0;i<=n;i++) { while(a[i]!=a[j+1]&&j)j=ne[j]; j+=(a[i]==a[j+1]),ne[i]=j; } if(!ne[n]) { cout<<n; return 0; } for(int i=ne[n];i;i=ne[i]) num[++sum]=i; f=find(); if(check(num[f])) cout<<num[f]; else cout<<n; } ``` ## 结 **蒟蒻码字不易,不喜勿喷。** **最后附上**[**文章链接**](https://www.luogu.com.cn/article/9a36etgv)