LOJ#10045. 「一本通 2.2 练习 1」Radio Transmission

UPD:更新了一种推导方法,但原推导方法没有删除。

为下文表述方便,这里做出一些约定:

  1. 字符串下标从 (1) 开始
  2. (l) 代表题目给定字符串 (s) 的长度;
  3. (operatorname{next}) 代表 KMP 算法的失配函数,(operatorname{next}(i)) 表示 (s_{1dots i}) 的最长公共前缀后缀的长度;
  4. 对于两个字符串 (s_1,s_2)(s_1+s_2) 代表将两个字符串拼接起来,如 (mathtt{abc}+mathtt{defg}=mathtt{abcdefg})

题目传送门

推导方法一:

令最短循环节为 (s_1),字符串结尾残缺的一段循环节为 (s_2)(s_2) 变成循环节需要补上的串为 (s_3)。根据定义,可得 (s_1=s_2+s_3)

有了上面的定义,这道题给出的字符串 (s) 就可以表示为 (s_1,s_1,dots,s_1,s_2) 的形式。将 (s) 中的所有 (s_1) 拆成 (s_2)(s_3),此时 (s=s_2,s_3,s_2,s_3,cdots,s_2,s_3,s_2)

通过这一些列拆分,我们很容易得出 (s) 的最长公共前缀后缀:( lap{overbrace{phantom{s_2,s_3,s_2,s_3,cdots,s_2}}^{ ext{prefix}}}s_2,s_3,underbrace{s_2,s_3,cdots,s_2,s_3,s_2}_{ ext{suffix}})

观察发现当 (s) 扔掉后缀(suffix)后,剩下 (s_2)(s_3),放在一起就是 (s_1),也就是我们要求的循环节。

综上,最短循环节长度为 (l-operatorname{next}(l))


推导方法二:

请确保用 KMP 算法做完 POJ2406 了之后再读这个推导方法。

从 POJ2406 这道题,我们知道:对于一个长度为 (l) 的字符串 (s)(假设它的下标从 (1) 开始),它的最短循环节长度是 (l-operatorname{next}(l))

虽然这道题的字符串是不完整的,但上面的结论仍然成立。下面我们来推导一下为什么:

(s=mathtt{abcabcabc}),这时 (s_{1dots 6}=s_{4dots 9})(operatorname{next}(9)=6)
去掉 (s) 的最后一个字符 (mathtt c),此时 (s=mathtt{abcabcab})(s_{1dots 5}=s_{4dots 8})(operatorname{next}(8)=5)
再去掉 (s) 的最后一个字符 (b),此时 (s=mathtt{abcabca})(s_{1dots 4}=s_{4dots 7})(operatorname{next}(7)=4)

发现什么规律了吗?每当我们将 (s) 的长度减一,(operatorname{next}(l)) 也会随着减一,(l-operatorname{next}(l)) 一直不变,算出的一直是完整字符串的答案。

配合着图再仔细理解一下:

至于 (operatorname{next}(l)) 为什么会随着长度递减,多看看这张图大概就理解了……

说了这么多,来看看这道题的完整代码:(其实核心代码只有一行qaq)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e6;
char s[N+10];
int nxt[N+10],n;
void init()
{
	memset(nxt,0,sizeof(nxt));
	int n=strlen(s);
	for(int i=1;i<n;i++)
	{
		int j=nxt[i-1];
		while(j && s[i]!=s[j]) j=nxt[j-1];
		if(s[i]==s[j]) j++;
		nxt[i]=j;
	}
}
int main()
{
	scanf("%d%s",&n,s);
	init();
	printf("%d
",n-nxt[n-1]);
	return 0;
}
原文地址:https://www.cnblogs.com/juruo-zzt/p/13325195.html