题解 P8112 【[Cnoi2021]符文破译】(Z 函数,单调队列优化 dp)
场上的小丑做法不请自来。
开题:SAM 的萌萌题?这数据范围的不对劲啊。哦,Z 函数就能做了,我降智了。(此时还没意识到自己还是降智了)
problem
给定字符串 \(\texttt{S}\) 和 \(\texttt{T}\),要求将 \(\texttt{S}\) 划分成最少的段,且每段是 \(\texttt{T}\) 的前缀。(\(1\leq|\texttt{S}||\texttt{T}|\leq 10^7\))
solution
正着不好做,考虑倒着 dp,\(lim_i\) 表示 \(\texttt{S}_{[i,n]}\) 与 \(\texttt{T}\) 的最长公共前缀,设 \(f_i\) 表示 \(\texttt{S}_{[i,n]}\) 最少的划分,则转移方程为:\(f_i=\min_{i<j\leq i+lim_i}\{f_j\}+1\)。
\(lim\) 可以用 Z-box 求,然后 \(O(n^2)\) 和 \(O(n\log n)\) 做法就显然了,但是这题要求线性。
分析一下,若:\(i<i^{\prime}\) 且 \(i+lim_i>i^{\prime}+lim_{i^{\prime}}\),那么从 \(i^{\prime}\) 进行划分一定不优,这里的 \(i^{\prime}\) 可以直接不用考虑。
把上述的位置扔掉后,\(i+lim_i\) 就是单调的了,可以使用单调队列进行优化,时间复杂度 \(O(n)\)。
比 kmp 更优美。
code
#include<bits/stdc++.h>
using namespace std;
#define rg register
#define ll long long
#define ull unsigned ll
#define inf 0x3f3f3f3f
#define djq 998244353
#define lowbit(x) (x&(-x))
inline void file(){
freopen("baka.in","r",stdin);
freopen("baka.out","w",stdout);
}
char buf[1<<21],*p1=buf,*p2=buf;
inline int getc(){
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
rg int ret=0,f=0;char ch=getc();
while(!isdigit(ch)){if(ch==EOF)exit(0);if(ch=='-')f=1;ch=getc();}
while(isdigit(ch)){ret=ret*10+ch-48;ch=getc();}
return f?-ret:ret;
}
int n,m,lim[10000005],z[10000005],f[10000005];;
char s[10000005],t[10000005];
int q[10000005],fr,re;
signed main(){
//file();
scanf("%d%d%s%s",&m,&n,t+1,s+1);
z[1]=m;
for(rg int i=2,l=0,r=0;i<=m;++i){
if(i<=r) z[i]=min(r-i+1,z[i-l+1]);
else z[i]=0;
while(i+z[i]<=m&&t[1+z[i]]==t[i+z[i]]) ++z[i];
if(r<i+z[i]-1) l=i,r=i+z[i]-1;
}
for(rg int i=1,l=0,r=0;i<=n;++i){
if(i<=r) lim[i]=min(r-i+1,z[i-l+1]);
else lim[i]=0;
while(i+lim[i]<=n&&1+lim[i]<=m&&t[1+lim[i]]==s[i+lim[i]]) ++lim[i];
if(r<i+lim[i]-1) l=i,r=i+lim[i]-1;
}
for(rg int i=1,j=0;i<=n;++i) j>i+lim[i]?lim[i]=inf:j=i+lim[i];
memset(f,0x3f,sizeof(f));
f[n+1]=0; q[fr=re=1]=n+1;
for(rg int i=n;i>=1;--i){
if(lim[i]==inf) continue;
while(fr-1!=re&&q[fr]>i+lim[i]) ++fr;
const int j=q[fr];
f[i]=f[j]+1;
while(fr-1!=re&&f[q[re]]>=f[i]) --re;
q[++re]=i;
}
if(f[1]>=0x3f3f3f3f) puts("Fake");
else printf("%d",f[1]);
return 0;
}
/*
*/