最短循环节问题
最短循环节问题
定义
循环节与最短循环节:若某个字符串是由某个子串循环构成的,那么就称该子串为原串的循环节,长度最短的循环节就是最短循环节。
ne[i]:表示字符串长度为i前缀的最长公共前后缀的长度。
s[i,j]:表示s下标i到j的字串,s下标从1开始。
定理
定理1:字符串s的所有循环节长度必定是最短循环节长度的倍数。
证明:
令\(T\)=最短循环节长度,\(T'\)=不是最短的循环节的长度,\(d=(T,T')\)。假设存在\(T'>T且T\not|T'\),则可知\(d<=T\)且\(d\not=T\)所以\(d<T\)。因为\(T,T'\)都是循环节长度,所以:
\[s_i=s_{i+T}=...=s_{i+aT}\\
s_i=s_{i+T'}=...=s_{i+bT'}\\
\]
因为\(d=(T,T')\)所以一定存在\(a, b使aT+bT'=d\),则:
\[s_i=s_{i+aT+bT'}=s_{i+d}
\]
则s存在一个长度为\(d\)且\(d<T\)的循环节,这与\(T\)为最短循环节长度矛盾。
故字符串s的所有循环节长度必定是最短循环节长度的倍数。
定理2:若\((i-ne[i])|i\)则字串s[1,i]存在一个长度为\((i-ne[i])\)的循环节,且此为最短循环节。
先证明\(s[1,(i-ne[i])]\)是循环节:
\[根据ne[i]的性质,s[1,ne[i]]=s[i-ne[i]+1,i]\\
则s[1,i-ne[i]] = s[i-ne[i]+1, 2i-2ne[i]]\\
又s[i-ne[i]+1,2i-2ne[i]] = s[2i-2ne[i]+1,3i-3ne[i]]\\
...\\
可得s[1,i-ne[i]] = s[i-ne[i+1],2i-2ne[i]]=...=s[k(i-ne[i]),i]
\]
得证。
再证明\(s[1,(i-ne[i])]\)是最短循环节:
令\(T=i-ne[i]\),\(T'\)是最短循环节的长度,则s必定存在公共前后缀长度为\(|s|-T'>|s|-T=ne[i]\)这与\(ne[i]\)的定义矛盾。
例题和代码
141. 周期
AC代码:
#include <iostream>
using namespace std;
const int N = 1000010;
char s[N];
int ne[N], n;
void solve()
{
cin >> s+1;
for(int i=2,j=0;i<=n;i++)
{
while(j && s[j+1] != s[i]) j =ne[j];
if(s[j+1] == s[i]) j++;
ne[i] = j;
//cout << ne[i] <<endl;
}
for(int i=2;i<=n;i++)
if(i%(i-ne[i]) == 0 && ne[i])
cout << i << ' ' << i/(i-ne[i]) << '\n';
}
int main(void)
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int cnt = 0;
while(cin >> n && n)
{
cout << "Test case #" << ++cnt << '\n';
solve();
cout << '\n';
}
}

浙公网安备 33010602011771号