KMP-Acwing-週期
題目:
一個字符串的前綴是從第一個字符開始的連續若干個字符,例如”abaab”共有5個前綴,分別是a, ab, aba, abaa, abaab。
我們希望知道一個N位字符串S的前綴是否具有循環節。
換言之,對於每一個從頭開始的長度爲 i (i>1)的前綴,是否由重複出現的子串A組成,即 AAA…A (A重複出現K次,K>1)。
如果存在,請找出最短的循環節對應的K值(也就是這個前綴串的所有可能重複節中,最大的K值)。
輸入格式
輸入包括多組測試數據,每組測試數據包括兩行。
第一行輸入字符串S的長度N。
第二行輸入字符串S。
輸入數據以只包括一個0的行作爲結尾。
輸出格式
對於每組測試數據,第一行輸出 “Test case #” 和測試數據的編號。
接下來的每一行,輸出具有循環節的前綴的長度i和其對應K,中間用一個空格隔開。
前綴長度需要升序排列。
在每組測試數據的最後輸出一個空行。
數據範圍
2≤N≤1000000
輸入樣例:
3
aaa
4
abcd
12
aabaabaabaab
0
輸出樣例:
Test case #1
2 2
3 3
Test case #2
Test case #3
2 2
6 2
9 3
12 4
題意:
求 給 定 長 度 爲 n 的 字 符 串 的 n 個 前 綴 中 , 長 度 爲 i 的 “ 循 環 節 ” , 以 及 “ 循 環 節 ” 的 個 數 n [ i ] , 1 < = i < = n 2 。 求給定長度爲n的字符串的n個前綴中,長度爲i的“循環節”,以及“循環節”的個數n[i],1<=i<=\frac{n}{2}。 求 給 定 長 度 爲 n 的 字 符 串 的 n 個 前 綴 中 , 長 度 爲 i 的 “ 循 環 節 ” , 以 及 “ 循 環 節 ” 的 個 數 n [ i ] , 1 < = i < = 2 n 。
樣 例 : a a b a a b a a b a a b 長 度 爲 2 的 前 綴 " a a " 的 循 環 節 爲 a , 有 2 個 。 長 度 爲 6 的 前 綴 " a a b a a b " 的 循 環 節 爲 a a b , 有 2 個 。 長 度 爲 9 的 前 綴 " a a b a a b a a b " 的 循 環 節 爲 a a b , 有 3 個 。 長 度 爲 12 的 前 綴 " a a b a a b a a b a a b " 的 循 環 節 爲 a a b , 有 4 個 。 樣例:aabaabaabaab\\長度爲2的前綴"aa"的循環節爲a,有2個。\\長度爲6的前綴"aabaab"的循環節爲aab,有2個。\\長度爲9的前綴"aabaabaab"的循環節爲aab,有3個。\\長度爲12的前綴"aabaabaabaab"的循環節爲aab,有4個。 樣 例 : a a b a a b a a b a a b 長 度 爲 2 的 前 綴 " a a " 的 循 環 節 爲 a , 有 2 個 。 長 度 爲 6 的 前 綴 " a a b a a b " 的 循 環 節 爲 a a b , 有 2 個 。 長 度 爲 9 的 前 綴 " a a b a a b a a b " 的 循 環 節 爲 a a b , 有 3 個 。 長 度 爲 1 2 的 前 綴 " a a b a a b a a b a a b " 的 循 環 節 爲 a a b , 有 4 個 。
題解:
首 要 問 題 是 如 何 找 出 循 環 節 。 首要問題是如何找出循環節。 首 要 問 題 是 如 何 找 出 循 環 節 。
通 過 K M P 算 法 : 通過KMP算法: 通 過 K M P 算 法 :
K M P 算 法 中 : 數 組 N e x t [ j ] 的 含 義 指 前 j − 1 個 字 符 構 成 的 子 串 的 最 長 相 同 前 後 綴 的 長 度 , 設 N e x t [ j ] = l 。 KMP算法中:數組Next[j]的含義指前j-1個字符構成的子串的最長相同前後綴的長度,設Next[j]=l。 K M P 算 法 中 : 數 組 N e x t [ j ] 的 含 義 指 前 j − 1 個 字 符 構 成 的 子 串 的 最 長 相 同 前 後 綴 的 長 度 , 設 N e x t [ j ] = l 。
那 麼 可 知 紅 色 部 分 前 綴 與 藍 色 部 分 後 綴 長 度 相 等 , 都 爲 l 。 從 而 可 知 綠 色 圈 圈 部 分 的 兩 條 線 段 長 度 相 等 。 那麼可知紅色部分前綴與藍色部分後綴長度相等,都爲l。從而可知綠色圈圈部分的兩條線段長度相等。 那 麼 可 知 紅 色 部 分 前 綴 與 藍 色 部 分 後 綴 長 度 相 等 , 都 爲 l 。 從 而 可 知 綠 色 圈 圈 部 分 的 兩 條 線 段 長 度 相 等 。
下 面 一 根 線 段 是 紅 色 部 分 前 綴 , 將 其 平 移 , 每 一 段 均 與 藍 色 後 綴 部 分 對 應 相 等 , 下面一根線段是紅色部分前綴,將其平移,每一段均與藍色後綴部分對應相等, 下 面 一 根 線 段 是 紅 色 部 分 前 綴 , 將 其 平 移 , 每 一 段 均 與 藍 色 後 綴 部 分 對 應 相 等 ,
即 2 = 5 , 3 = 6 , 4 = 7 , 由 1 = 5 知 1 = 2 , 由 2 = 6 知 2 = 3 , 由 3 = 7 知 3 = 4 , 所 以 1 = 2 = 3 = 4 , 1 , 2 , 3 , 4 即 該 字 符 串 的 循 環 節 。 即2=5,3=6,4=7,由1=5知1=2,由2=6知2=3,由3=7知3=4,所以1=2=3=4,\\1,2,3,4即該字符串的循環節。 即 2 = 5 , 3 = 6 , 4 = 7 , 由 1 = 5 知 1 = 2 , 由 2 = 6 知 2 = 3 , 由 3 = 7 知 3 = 4 , 所 以 1 = 2 = 3 = 4 , 1 , 2 , 3 , 4 即 該 字 符 串 的 循 環 節 。
於 是 前 j − 1 個 子 串 的 循 環 節 的 最 小 長 度 就 是 j − N e x t [ j ] , 這 必 然 是 最 小 的 循 環 節 , 這 是 因 爲 N e x t [ j ] 數 組 存 儲 的 是 相 同 前 後 綴 的 最 大 長 度 。 於是前j-1個子串的循環節的最小長度就是j-Next[j],這必然是最小的循環節,\\這是因爲Next[j]數組存儲的是相同前後綴的最大長度。 於 是 前 j − 1 個 子 串 的 循 環 節 的 最 小 長 度 就 是 j − N e x t [ j ] , 這 必 然 是 最 小 的 循 環 節 , 這 是 因 爲 N e x t [ j ] 數 組 存 儲 的 是 相 同 前 後 綴 的 最 大 長 度 。
循 環 節 的 個 數 即 : 子 串 長 度 i / 循 環 節 長 度 t 。 循環節的個數即: 子串長度i/循環節長度t。 循 環 節 的 個 數 即 : 子 串 長 度 i / 循 環 節 長 度 t 。
注 意 : 判 定 是 否 爲 循 環 節 還 需 判 斷 t 能 否 整 除 i , 若 不 能 , 則 說 明 不 能 形 成 循 環 節 。 注意:判定是否爲循環節還需判斷t能否整除i,若不能,則說明不能形成循環節。 注 意 : 判 定 是 否 爲 循 環 節 還 需 判 斷 t 能 否 整 除 i , 若 不 能 , 則 說 明 不 能 形 成 循 環 節 。
代碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N= 1e6 + 10 ;
int Next[ N] ;
char str[ N] ;
int n;
void get_table ( )
{
for ( int i= 2 , j= 0 ; i<= n; i++ )
{
while ( j&& str[ i] != str[ j+ 1 ] ) j= Next[ j] ;
if ( str[ i] == str[ j+ 1 ] ) j++ ;
Next[ i] = j;
}
}
int main ( )
{
int T= 1 ;
while ( ~ scanf ( "%d" , & n) , n)
{
scanf ( "%s" , str+ 1 ) ;
get_table ( ) ;
printf ( "Test case #%d\n" , T++ ) ;
for ( int i= 2 ; i<= n; i++ )
{
int t = i - Next[ i] ;
if ( i > t && i % t == 0 ) printf ( "%d %d\n" , i, i / t) ;
}
puts ( "" ) ;
}
return 0 ;
}