最短循环节问题

最短循环节问题

定义

循环节与最短循环节:若某个字符串是由某个子串循环构成的,那么就称该子串为原串的循环节,长度最短的循环节就是最短循环节。
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';
    }
        
}

posted @ 2023-02-26 14:24  DarkLights  阅读(149)  评论(0)    收藏  举报