對於任何資訊,人類總有一種衝動,就是找到其最本質的組成。例如對於所有的數字,我們會去研究質數,那是因為質數可不可再分解的,於是任何整數都可以寫成質因子連乘的形式。對於字串,看似無規律,但由於語法上的原因,事實上許多字串其用到的字元種類是不太多的,也就是說字母表中的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範圍,程式碼略過。