洛谷3426 [POI2005]SZA-Template 恶臭dp+kmp

前言

做这道题时,我和巨神yxc在洛咕上看到了一篇代码奇短的题解,然后看解析,发现里面的证明都是“显然”、“很简单”,被臭到。
于是zyd和yxc爆肝了2h左右,终于肝出了这个方法的证明,蜃臭。

题目链接:传送门

Byteasar想在墙上涂一段很长的字符,他为了做这件事从字符的前面一段中截取了一段作为模版. 然后将模版重复喷涂到相应的位置后就得到了他想要的字符序列.一个字符可以被覆盖很多次,但是一个位置不能填不同的字符.做一个模版很费工夫,所以他想要模版的长度尽量小,求最小长度是多少。

先用kmp求出字符串的 n e x t next next数组,然后dp。(以下 n e x t next next记为 n x t nxt nxt
f [ i ] f[i] f[i]表示前缀为 i i i的模板长度的最小值。
先证明几个引理。

注明:
用来覆盖一个序列的模版一定是字符串的前缀,但在下文中有些地方称为“长度为XXX的串”,意思是"长度为XXX的前缀"。

引理一、 f [ i ] f[i] f[i]的取值只能为 i i i或者 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]

引理1.1 f [ i ] < = i f[i]<=i f[i]<=i
这个很显然。。因为用这个字符串本身一定能覆盖前 i i i个字符qwq

引理1.2 f [ i ] > = f [ n x t [ i ] ] f[i]>=f[nxt[i]] f[i]>=f[nxt[i]]
如果 f [ i ] < f [ n x t [ i ] ] f[i]<f[nxt[i]] f[i]<f[nxt[i]],那么就无法用 f [ i ] f[i] f[i]的长度来填充前 n x t [ i ] nxt[i] nxt[i]个字符(否则 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]会更小,就矛盾了)

引理1.3 若 f [ i ] < i f[i]<i f[i]<i,则 f [ i ] < = n x t [ i ] f[i]<=nxt[i] f[i]<=nxt[i]
证明:如果 f [ i ] > n x t [ i ] f[i]>nxt[i] f[i]>nxt[i],则长度为 f [ i ] f[i] f[i]时,能使此字符串的前后缀相同(如图所示,两段红色的区域是相同的),因为必须有一个长度 f [ i ] f[i] f[i]的串在最前面,有一个长度 f [ i ] f[i] f[i]的串在最后面,否则前面或后面会覆盖不到qwq。
在这里插入图片描述
这与 n x t [ i ] nxt[i] nxt[i]是最大的使前后缀相同的长度矛盾,所以 f [ i ] < = n x t [ i ] f[i]<=nxt[i] f[i]<=nxt[i]

引理1.4 若 f [ i ] < n x t [ i ] f[i]<nxt[i] f[i]<nxt[i],那么 f [ i ] = f [ n x t [ i ] ] f[i]=f[nxt[i]] f[i]=f[nxt[i]]
证明:
f [ n x t [ i ] ] < f [ i ] < n x t [ i ] f[nxt[i]]<f[i]<nxt[i] f[nxt[i]]<f[i]<nxt[i]时,覆盖情况如图所示。
在这里插入图片描述
f [ i ] f[i] f[i]的定义,能用长度为 f [ i ] f[i] f[i]的串来覆盖完前 i i i个字符,且能用长度 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]的串覆盖前 n x t [ i ] nxt[i] nxt[i]个字符。
所以珂以用前 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]的串来覆盖长度为 f [ i ] f[i] f[i]的串(因为 f [ i ] < n x t [ i ] f[i]<nxt[i] f[i]<nxt[i]),这与 f [ i ] f[i] f[i]是最小的能覆盖前 i i i个字符的长度矛盾。
因此 f [ n x t [ i ] ] < f [ i ] < n x t [ i ] f[nxt[i]]<f[i]<nxt[i] f[nxt[i]]<f[i]<nxt[i]不成立。
由引理1.2, f [ i ] > = f [ n x t [ i ] ] f[i]>=f[nxt[i]] f[i]>=f[nxt[i]]。由已知, f [ i ] < n x t [ i ] f[i]<nxt[i] f[i]<nxt[i]
所以 f [ i ] = f [ n x t [ i ] ] f[i]=f[nxt[i]] f[i]=f[nxt[i]]

引理1.5 若 f [ i ] = n x t [ i ] f[i]=nxt[i] f[i]=nxt[i],则 f [ n x t [ i ] ] = n x t [ i ] f[nxt[i]]=nxt[i] f[nxt[i]]=nxt[i],即 f [ i ] = f [ n x t [ i ] ] f[i]=f[nxt[i]] f[i]=f[nxt[i]]
图与引理1.4类似,就不画了qwq。
因为 f [ i ] = n x t [ i ] f[i]=nxt[i] f[i]=nxt[i],所以珂以用长度 n x t [ i ] nxt[i] nxt[i]的串来覆盖前 i i i个字符,且不能用长度比 n x t [ i ] nxt[i] nxt[i]小的串来覆盖前 i i i个。
因此前 n x t [ i ] nxt[i] nxt[i]个字符和后 n x t [ i ] nxt[i] nxt[i]个字符之间的部分(注意这部分可能为空,即前后 n x t [ i ] nxt[i] nxt[i]个可能重叠)也可以用长度为 n x t [ i ] nxt[i] nxt[i]的前缀来覆盖。
f [ n x t [ i ] ] ≠ n x t [ i ] f[nxt[i]] \not=nxt[i] f[nxt[i]]=nxt[i],由引理1.1,只可能是 f [ n x t [ i ] ] < n x t [ i ] f[nxt[i]]<nxt[i] f[nxt[i]]<nxt[i]的情况。
因为可以用长度为 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]的串来覆盖前 n x t [ i ] nxt[i] nxt[i]个。
因此珂以把覆盖前 i i i个字符的长度为 n x t [ i ] nxt[i] nxt[i]的串都换成长度为 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]的串。
此时 f [ i ] f[i] f[i]应为 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]],而 f [ n x t [ i ] ] < f [ i ] f[nxt[i]]<f[i] f[nxt[i]]<f[i],矛盾。
因此 f [ i ] = f [ n x t [ i ] ] = n x t [ i ] f[i]=f[nxt[i]]=nxt[i] f[i]=f[nxt[i]]=nxt[i]

综上,
由引理1.1和引理1.2,有 f [ n x t [ i ] ] < = f [ i ] < = i f[nxt[i]]<=f[i]<=i f[nxt[i]]<=f[i]<=i
由引理1.3,有 f [ n x t [ i ] ] < = f [ i ] < = n x t [ i ] f[nxt[i]]<=f[i]<=nxt[i] f[nxt[i]]<=f[i]<=nxt[i] f [ i ] = i f[i]=i f[i]=i
由引理1.4和引理1.5,当 f [ i ] < = n x t [ i ] f[i]<=nxt[i] f[i]<=nxt[i]时, f [ i ] = f [ n x t [ i ] ] f[i]=f[nxt[i]] f[i]=f[nxt[i]]
因此 f [ i ] = f [ n x t [ i ] ] f[i]=f[nxt[i]] f[i]=f[nxt[i]] f [ i ] = i f[i]=i f[i]=i

引理二、 f [ i ] = f [ n x t [ i ] ] f[i]=f[nxt[i]] f[i]=f[nxt[i]]的充要条件是存在 j ∈ [ i − n x t [ i ] , i ) j \in [i-nxt[i],i) j[inxt[i],i) f [ j ] = f [ n x t [ i ] ] f[j]=f[nxt[i]] f[j]=f[nxt[i]]

引理2.1 引理二的充分性证明,即 f [ i ] = f [ n x t [ i ] ] = > ∃ j ∈ [ i − n x t [ i ] , i ] , f [ j ] = f [ n x t [ i ] ] f[i]=f[nxt[i]] => \exist j\in[i-nxt[i],i],f[j]=f[nxt[i]] f[i]=f[nxt[i]]=>j[inxt[i],i],f[j]=f[nxt[i]]
在这里插入图片描述
[ i − n x t [ i ] , i ] [i-nxt[i],i] [inxt[i],i]之间任意一处取一个 j j j,发现 [ 1 , j ] [1,j] [1,j]之间一定能被红色线段覆盖。
在这里插入图片描述
因此可以证明引理二的充分性。

引理2.2 引理二的必要性证明,即 ∃ j ∈ [ i − n x t [ i ] , i ] , f [ j ] = f [ n x t [ i ] ] = > f [ i ] = f [ n x t [ i ] ] \exist j\in[i-nxt[i],i],f[j]=f[nxt[i]]=>f[i]=f[nxt[i]] j[inxt[i],i],f[j]=f[nxt[i]]=>f[i]=f[nxt[i]]
图与引理2.1的证明类似,就不画了qwq。
因为 ∃ j ∈ [ i − n x t [ i ] , i ] , f [ j ] = f [ n x t [ i ] ] \exist j\in[i-nxt[i],i],f[j]=f[nxt[i]] j[inxt[i],i],f[j]=f[nxt[i]],且由 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]的定义,长度为 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]的串能覆盖前 n x t [ i ] nxt[i] nxt[i]个字符,
所以 ( i − n x t [ i ] , i ] (i-nxt[i],i] (inxt[i],i]之间的字符也可以用 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]的长度的串覆盖。
又因为 [ 1 , j ] [1,j] [1,j]的字符可以用 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]的长度的串覆盖,且 j > = i − n x t [ i ] j>=i-nxt[i] j>=inxt[i]
所以 [ 1 , i ] [1,i] [1,i]的字符可以用 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]的长度的串覆盖。
由引理一, f [ i ] = f [ n x t [ i ] ] f[i]=f[nxt[i]] f[i]=f[nxt[i]] i i i
由引理1.1, f [ n x t [ i ] ] < = n x t [ i ] < i f[nxt[i]]<=nxt[i]<i f[nxt[i]]<=nxt[i]<i,即 f [ n x t [ i ] ] < i f[nxt[i]]<i f[nxt[i]]<i
所以 f [ i ] = f [ n x t [ i ] ] f[i]=f[nxt[i]] f[i]=f[nxt[i]]

由引理2.1和引理2.2,引理二得证。

重复一次引理:
引理一、 f [ i ] = f [ n x t [ i ] ] 或 i f[i]=f[nxt[i]]或i f[i]=f[nxt[i]]i f [ n x t [ i ] ] < i f[nxt[i]]<i f[nxt[i]]<i
引理二、 f [ i ] = f [ n x t [ i ] ] < = > 存 在 j ∈ [ i − n x t [ i ] , i ] , f [ j ] = f [ n x t [ i ] ] f[i]=f[nxt[i]]<=>存在j\in[i-nxt[i],i],f[j]=f[nxt[i]] f[i]=f[nxt[i]]<=>j[inxt[i],i]f[j]=f[nxt[i]]
然后看这道题:
先kmp跑出 n x t nxt nxt数组,然后用一个数组记录每个 f f f值出现的最新位置。
遍历1~n,若 f [ n x t [ i ] ] f[nxt[i]] f[nxt[i]]出现的最新位置 > = i − n x t [ i ] >=i-nxt[i] >=inxt[i],则 f [ i ] = f [ n x t [ i ] ] f[i]=f[nxt[i]] f[i]=f[nxt[i]],否则为 i i i

毒瘤代码

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#define re register int
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=500005;
int n,b[Size],f[Size],nxt[Size];
char str[Size];
int main() {
//	n=read();
	scanf("%s",str+1);
	n=strlen(str+1);
	int j=0;
	for(re i=2; i<=n; i++) {
		while(j>0 && str[j+1]!=str[i])	j=nxt[j];
		if(str[j+1]==str[i])	j++;
		nxt[i]=j;
	}
	for(re i=2; i<=n; i++) {
		f[i]=i;
		if(b[f[nxt[i]]]>=i-nxt[i]) {
			f[i]=f[nxt[i]];
		}
		b[f[i]]=i;
	}
	printf("%d",f[n]);
	return 0;
}
  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值