關於最小循環節的幾種求法[原創]


 關於最小循環節的幾種求法

鄒毅

對於任何信息,人類總有一種沖動,就是找到其最本質的組成。例如對於所有的數字,我們會去研究質數,那是因為質數可不可再分解的,於是任何整數都可以寫成質因子連乘的形式。對於字符串,看似無規律,但由於語法上的原因,事實上許多字符串其用到的字符種類是不太多的,也就是說字母表中的26個字母出現的頻率是不一樣的。於是人類開始研究最小循環節,即某個字符串是不是由某個循環節字符串拼接而成。我們來看下面這個例題:

 

 Pku2406 Power Strings

求一個字符串由多少個重復的子串連接組成,例如ababab由3個ab連接而成,因此答案為3,又例如abcd由1個abcd連接而成,因此答案為1

 

Format

Input

多組數據,以"."代表測試結束 每組數據給出的字符串長度 <=1e6

 

Output

如題

 

樣例輸入

abcd

aaaa

ababab

.

 

樣例輸出

1

4

3

 

題解1:

對於這個題,我們設讀入的字符串存在字符數組s中,設其長度為len.

於是可以枚舉所求的循環節長度為i,即字符數組的前i個字符構成了循環節,然后就可以來進行校驗了。由於此處涉及字符的比較,於是使用hash。

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
using namespace std;
typedef unsigned long long LL;
const LL base=131;
const int N=1000010;
int n;
LL power[N],sum[N];
bool check(LL v,int k)  //判斷s[1]~s[k]是否是循環節
{
    for(register int i=1;i+k-1<=n;i+=k){
        if(v!=sum[i+k-1]-sum[i-1]*power[k]) return 0;
    }
    return 1;
}
int main()
{
    power[0]=1;
    for(register int i=1;i<=N-10;++i) //hash准備工作
        power[i]=power[i-1]*base;
    char s[N];
    while(scanf("%s",s+1)){
        if(s[1]=='.')break;
        n=strlen(s+1);
        sum[0]=0;
        for(register int i=1;i<=n;++i) sum[i]=sum[i-1]*base+LL(s[i]);
        for(register int i=1;i<=n;++i){
            if(n%i)continue;
            LL expect=sum[i];
            if(check(expect,i)){
                printf("%d\n",n/i);
                break;
            }   
        }
    }
    return 0;
}

  

題解2:

在上種做法中,我們設循環節長度為i ,當然i必然為len的約數。於是整個字符串分成了len/i份。然后逐個逐個比較過去。大膽猜想一下,能否不要比較這么多次呢?

我們來畫個圖看看,對於字符串s划分如下:

 

 

 

為了區分,這幾段標上了不同的顏色。

如果第一段為我們所求的循環節,則我們將s復寫一次,並右移i 位

 

 

如果a2—a5這一段等於下面的a1—a4這一段,則可知

A2=a1,a3=a2,a4=a3,a5=a4.

於是循環節為A1.

分析出這個性質后,我們只需要一次字符之間的對比,就可以知道字符串的某個前綴是不是整個字符串的循環節了。

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
char a[2000000];
int len=0;
ull sum[2000000],power[2000000];
ull get(int l,int r)
{
    return sum[r]-sum[l-1]*power[r-l+1];
}
int main()
{
    while(true)
    {
        scanf("%s",a+1);
        len=strlen(a+1);
        if(a[1]=='.'&&len==1)break;
        memset(sum,0,sizeof(sum));
        memset(power,0,sizeof(power));
        for(int i=1;i<=len;i++)
             sum[i]=sum[i-1]*193+ull(a[i])+1;
        power[0]=1;
        for(int i=1;i<=len;i++)
             power[i]=power[i-1]*193;
        for(int i=1;i<=len;i++) //暴力枚舉循環節的長度
        {
            if(len%i!=0)continue;
            else
            {
                ull a1=get(1,len-i),a2=get(i+1,len);
              //注意是取長度為len-i的前綴,看是否等於長度為len-i的后綴
                if(a1==a2)
                {
                    printf("%d\n",len/i);//得到循環節的個數
                    break;
                }
            }
        }
    }
     return 0;
}

  

Sol3:

 題解2中,減少了比較的次數,看上去似乎沒有優化的地步了。我們將眼光轉向循環節的長度這個要素。在前面的做法中,我們都只要求循環節長度i為總長度len的約數即可,於是划分的段數 k=len/i,完全沒有考慮讀入字符串的構成這個因素。很明顯我們可以統計下字符串中每種字母出現的次數,不妨設之為sum1……sum26,當我們根據循環節將整個字符串划分成k段時,就是將這些字母“均分”到k段中,於是k至多為gcd(len,sum1,sum2….sum26),如果檢測不成功,則也應該為 gcd(len,sum1,sum2….sum26)的約數,至此我們較為精確的約束了k范圍.

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const ll M=1000010;
const ll b=193;
char c[M];
ll hh[M],sum[M],len,ans,num[30],gcd;
ll cut(ll l,ll r) {
	return sum[r]-sum[l-1]*hh[r-l+1];
}
bool check(ll k) {
	return cut(1,len-k)==cut(k+1,len);
}
main() {
	hh[0]=1;
	for(ll i=1; i<=M; i++)
		hh[i]=hh[i-1]*b;
	while(scanf("%s",c+1)) 
	{
		if(c[1]=='.')break;
		memset(num,0,sizeof num);
		sum[0]=0;
		len=strlen(c+1);
		for(ll i=1; i<=len; i++)
			sum[i]=sum[i-1]*b+ll(c[i]);
		for(int i=1; i<=len; i++)
			num[c[i]-'a'+1]++;
	//	for(int i=1;i<=26;i++)
	//		cout<<num[i]<<" ";
        gcd=len;
		for(int i=1; i<=26; i++)
	    	if (num[i])
			    gcd=__gcd(gcd,num[i]);
		//cout<<"gcd is  "<<gcd<<endl;
		for(ll i=gcd; i>=1; i--) 
		{
			if(gcd%i!=0)
			    continue;
			ll num=sum[len/i];
			if(check(len/i)) 
			{
				printf("%lld\n",i);
				break;
			}
		}
	}
}

  

 題后記:此題還可以用Kmp算法來完成,此其就不再細說了,有興趣的可以自行去研究。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM