LGP3426题解

真是不管什么时候来做这道题都会觉得很神仙呐。。。

观察一下,如果存在一个合法的印章,那么这个印章一定是这个串的前缀,也是这个串的后缀。

即合法的印章一定是原串的 \(\rm Border\)。

于是设 \(dp[n]\) 为 \([1,n]\) 这个前缀的最短的印章长度。这个印章就是 \([1,dp[n]]\)。

注意到 \(dp[fail[n]]\) 的长度不可能大于 \(dp[n]\),所以 \(dp[n]\) 其实只有两种决策:\(n\) 和 \(dp[fail[n]]\)。

于是开始思考满足什么条件的情况下才能够使用 \(dp[fail[n]]\)。

注意到 \([1,dp[fail[n]]]\) 一定是 \([1,n]\) 的后缀,所以我们假设这个印章已经把这个后缀狂暴轰入了,那么 \([1,n-dp[fail[n]]]\) 还没有被轰入。所以条件就是 \([1,n-dp[fail[n]]+1]\sim [1,n]\) 这几个前缀中的某一个的前缀的 \(dp\) 值为 \(dp[fail[n]]\)。(也就是轰入这个前缀之后,再使用这个印章轰入后缀。)

看似需要维护区间 \(\max\)?其实并不需要,只需要对每一个 \(dp\) 值维护最右边的下标即可。

代码很短,但是思维量是很多的。

#include<cstring>
#include<cstdio>
typedef unsigned ui;
const ui M=5e5+5;
ui n,dp[M],mx[M],fail[M];char s[M];
signed main(){
	ui i,j;scanf("%s",s+1);n=std::strlen(s+1);
	for(i=1,j=0;i<=n;++i){
		if(i==1)fail[i]=0,dp[i]=1,mx[1]=1;
		else{
			while(j&&s[j+1]!=s[i])j=fail[j];if(s[j+1]==s[i])++j;fail[i]=j;
			if(mx[dp[j]]>=i-j)dp[i]=dp[j];else dp[i]=i;mx[dp[i]]=i;
		}
	}
	printf("%u",dp[n]);
}
上一篇:开发十余年Android架构师教你如何突破瓶颈,达到年薪50W


下一篇:作为一个开发者,如何更好的学习鸿蒙?,高级android开发简历