KMP解決字符串最小循環節相關問題


經典問題 : 給出一個由某個循環節構成的字符串,要你找出最小的循環節,例如 abababab 最小循環節當是 ab ,而類似 abab 也可以成為它的循環節,但並非最短。

 

分析 :

對於上述問題有兩個結論 

如果對於next數組中的 i, 符合 i % ( i - next[i] ) == 0 && next[i] != 0 , 則說明字符串循環,而且

循環節長度為:    i - next[i]

循環次數為:       i / ( i - next[i] )

水平有限,用自己的語言描述怕有差錯,給出一個參考博客  ==>  http://www.cnblogs.com/jackge/archive/2013/01/05/2846006.html

 

再拋一個問題 : 有沒有想過對於一個不完整的循環串要補充多少個才能使得其完整?

答案是==>(循環節長度) - len%(循環節長度) 即 (len - next[len]) - len%(len - next[len])

為什么? (以下胡扯,看不懂就掠過吧.........)

首先考慮整串就是循環節構成的情況,類似 abcxabcx 觀察構造出來的next值顯然滿足上式,得出答案 0

那現在考慮不完整的情況,例如 abcabca 、其 next 值為 -1 0 0 0 1 2 3 4 。現在考慮末尾的 a,若沒有它,而是將 c 作為末尾則會在 len 失配的時候會回溯道下一個循環節的末尾即 abca , 那現在多了一個a,那么回溯當然也應該是(循環節長度 + 1) 即 abcab,故 len 那里無論是否剛好為循環節的末尾,只是個"殘"的末尾,未圓滿的循環節,len-next[len]也是循環節長度,那需要補多少個呢?現在就很顯然了!下面相關題目的 ① 就是這樣的一個問題。

 

相關題目 : 

HDU 3746 Cyclic Nacklace

題意 : 給出一個字符串,問你最少補充多少個字母才能使得字符串由兩個或者以上的循環節構成

分析 : 由結論可知,如果字符串循環,那么最小循環節的長度為 len - next[len] ,並且這個字符串總長能被循環節長度整除說明字符串已經循環,否則 len % (len - next[len]) 則為多出來的部分,例如 abcabcab ==> len - next[len] = 3,而 len % 3 == 2 很明顯就是余出來兩個,這兩個應當是循環節的頭兩個字母,對於其他串也可以自己模擬看看,所以需要補充的就是 循環節長度 - 多余出來的長度

#include<stdio.h> #include<string.h>
using namespace std; const int maxn = 1e5 + 10; char mo[maxn]; int Next[maxn], moL, nCase; inline void GetNext() { int i = 0, j = -1; Next[i] = j; while(i < moL){ while( j!=-1 && mo[i]!=mo[j]) j = Next[j]; Next[++i] = ++j; } } int ans() { GetNext(); if(Next[moL] == 0) return moL; int Period_len = moL - Next[moL]; int Remain = moL % Period_len; if(Remain == 0) return 0; return Period_len - Remain; } int main(void) { scanf("%d", &nCase); while(nCase--){ scanf("%s", mo); moL = strlen(mo); printf("%d\n", ans()); } return 0; }
View Code

 

POJ 1961 Period

題意 : 給出一個字符串,叫你給出這個字符串存在的不同循環節長度以及個數 ( 循環節構成的不一定是整個字符串,也有可能是其子串 )

分析 : 根據以上的結論,我們只要讓構造出字符串的next數組,而后一個for循環判斷當前長度和當前最小循環節長度是否是倍數關系,即 i % ( i - next[i] ) == 0 && next[i] != 0,就能判斷是否為一個循環節了,循環節的長度自然是 i / (i-next[i])

#include<stdio.h>
using namespace std; const int maxn = 1e6 + 10; char mo[maxn]; int Next[maxn], moL; inline void GetNext() { int i = 0, j = -1; Next[i] = j; while(i < moL){ while( j!=-1 && mo[j]!=mo[i] ) j = Next[j]; Next[++i] = ++j; } } inline void PrintAns() { GetNext(); int Period; for(int i=1; i<=moL; i++){ if(Next[i] != 0){ Period = i - Next[i]; if(i % Period == 0){ printf("%d %d\n", i, i/Period); } } }puts(""); } int main(void) { int Case = 1; while(~scanf("%d", &moL) && moL){ scanf("%s", mo); printf("Test case #%d\n", Case++); PrintAns(); } return 0; }
View Code

 

HUST 1010  The Minimum Length

題意 : 假設 A 是一個循環字符串,現在截取 A 的某一段子串 B 出來,給出 B 問你構成 A 的循環節的最小長度是多少?

分析 : 既然是循環串當中截取出來的,那么只要根據結論公式算出最小循環節長度即是答案,可以證明證明這樣做永遠是最優的。以下代碼由於HUST OJ崩了,所以不知道結果
#include<stdio.h> #include<string.h> #include<iostream>
using namespace std; const int maxn = 1e6 + 10; int Next[maxn], moL; char mo[maxn]; inline void GetNext() { int i = 0, j = -1; Next[i] = j; while(i < moL){ while(j!=-1 && mo[i]!=mo[j]) j = Next[j]; Next[++i] = ++j; } } int Ans() { GetNext(); if(Next[moL] == 0) return moL; else return moL - Next[moL]; } int main(void) { while(~scanf("%s", mo)){ moL = strlen(mo); printf("%d\n", Ans()); } return 0; }
View Code

 

POJ 2406 Power String

題意 : 給你一個字符串,問你它由多少個相同的字符串拼接而成

分析 : 直接找算出最小循環節長度,如果字符循環,則答案為 len / (循環節長度) ,而對於 len % (循環節長度) != 0 和 next[len] == 0 的情況答案就是 1 了

#include<string.h> #include<stdio.h>
using namespace std; const int maxn = 1e6 + 10; int Next[maxn], moL; char mo[maxn]; inline void GetNext() { int i = 0, j = -1; Next[i] = j; while(i < moL){ while(j!=-1 && mo[i]!=mo[j]) j = Next[j]; Next[++i] = ++j; } } int Ans() { GetNext(); if(Next[moL] == 0) return 1; int Period = moL - Next[moL]; if(moL % Period != 0) return 1; return moL / Period; } int main(void) { while(scanf("%s", mo) && mo[0]!='.'){ moL = strlen(mo); printf("%d\n", Ans()); } return 0; }
View Code

 

POJ 2752 Seek the Name, Seek the Fame

題意 : 給出一個字符串,問你所有關於這個字符串的前綴和后綴相同的長度,比如 abcab 有 1 "a"、2 "ab"、5 "abcab"

分析 : 這里就要巧妙利用到 next 數組的性質了,根據next數組定義可以知道 next[len] 表示一個從頭開始長度為 next[len] 的前綴和相同長度的后綴相等,那么next[ next[len] ]呢?next[ next[ next[len] ] ]呢?這里的一層層嵌套實際上都是一個長度為 next[ next[len] ] 或者 長度 next[ next[ next[len] ] ]的前綴和后綴相等,自己構造個數組畫畫圖也能得出來這個規律,那么到此,這個問題是不是被圓滿的解決了呢!

#include<string.h> #include<stack> #include<stdio.h>
using namespace std; const int maxn = 4e5 + 10; char mo[maxn]; int Next[maxn], moL; inline void GetNext() { int i = 0, j = -1; Next[i] = j; while(i < moL){ while(j!=-1 && mo[j]!=mo[i]) j = Next[j]; Next[++i] = ++j; } } inline void PrintAns() { moL = strlen(mo); GetNext(); int tmp = Next[moL]; stack<int> ans;///根據題目要求需要遞增輸出長度,而我們得出的答案順序正好相反,所以利用棧存儲
    while(tmp != -1){///直到頭為止
 ans.push(tmp); tmp = Next[tmp]; } while(!ans.empty()){ int Top = ans.top(); ans.pop(); if(Top) printf("%d ", Top); } printf("%d\n", moL); } int main(void) { while(~scanf("%s", mo)){ PrintAns(); } return 0; }
View Code

 

 


免責聲明!

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



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