题解 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;
}
/*

*/
posted @ 2022-02-09 16:44  Legitimity  阅读(70)  评论(0编辑  收藏  举报