@bzoj - 3670@ [Noi2014]動物園


@description@

近日,園長髮現動物園中好吃懶作的動物愈來愈多了。例如企鵝,只會賣萌向遊客要吃的。爲了整治動物園的不良風氣,讓動物們憑本身的真才實學向遊客要吃的,園長決定開設算法班,讓動物們學習算法。數組

某天,園長給動物們講解KMP算法。
園長:「對於一個字符串S,它的長度爲L。咱們能夠在O(L)的時間內,求出一個名爲next的數組。有誰預習了next數組的含義嗎?」
熊貓:「對於字符串S的前i個字符構成的子串,既是它的後綴又是它的前綴的字符串中(它自己除外),最長的長度記做next[i]。」
園長:「很是好!那你能舉個例子嗎?」
熊貓:「例S爲abcababc,則next[5]=2。由於S的前5個字符爲abcab,ab既是它的後綴又是它的前綴,而且找不到一個更長的字符串知足這個性質。同理,還可得出next[1] = next[2] = next[3] = 0,next[4] = next[6] = 1,next[7] = 2,next[8] = 3。」
園長表揚了認真預習的熊貓同窗。隨後,他詳細講解了如何在O(L)的時間內求出next數組。學習

下課前,園長提出了一個問題:「KMP算法只能求出next數組。我如今但願求出一個更強大num數組一一對於字符串S的前i個字符構成的子串,既是它的後綴同時又是它的前綴,而且該後綴與該前綴不重疊,將這種字符串的數量記做num[i]。例如S爲aaaaa,則num[4] = 2。這是由於S的前4個字符爲aaaa,其中a和aa都知足性質‘既是後綴又是前綴’,同時保證這個後綴與這個前綴不重疊。而aaa雖然知足性質‘既是後綴又是前綴’,但遺憾的是這個後綴與這個前綴重疊了,因此不能計算在內。同理,num[1] = 0,num[2] = num[3] = 1,num[5] = 2。」測試

最後,園長給出了獎勵條件,第一個作對的同窗獎勵巧克力一盒。聽了這句話,睡了一節課的企鵝馬上就醒過來了!但企鵝並不會作這道題,因而向參觀動物園的你尋求幫助。你可否幫助企鵝寫一個程序求出num數組呢?
特別地,爲了不大量的輸出,你不須要輸出num[i]分別是多少,你只須要輸出\(\prod_{i=1}^{L}(num[i]+1)\)對1,000,000,007取模的結果便可。spa

Input
第1行僅包含一個正整數n ,表示測試數據的組數。隨後n行,每行描述一組測試數據。每組測試數據僅含有一個字符串S,S的定義詳見題目描述。數據保證S 中僅含小寫字母。輸入文件中不會包含多餘的空行,行末不會存在多餘的空格。code

Output
包含 n 行,每行描述一組測試數據的答案,答案的順序應與輸入數據的順序保持一致。對於每組測試數據,僅須要輸出一個整數,表示這組測試數據的答案對 1,000,000,007 取模的結果。輸出文件中不該包含多餘的空行。ip

Sample Input
3
aaaaa
ab
abcababc
Sample Output
36
1
32字符串

HINT
n≤5,L≤1,000,000博客

@solution@

題面暗示題解.jpg。string

能夠以 border 的角度理解這個題,也能夠以 border 的角度更好地理解 kmp 算法。
什麼是 border?即字符串全部相同的前綴與後綴構成的集合,或者說 S[1...i] = S[lenS-i+1...lenS] 對應的 S[1...i]。
那麼 kmp 所求的實際上就是前 i 個字符對應的最長 border 長度 next[i]。

注意到 next[i], next[next[i]], ... 實際上包含了前 i 個字符的全部 border 長度。
那麼這道題就能夠用倍增求出第一個長度 <= i/2 的 border 長度 fir[k]。
咱們能夠在 kmp 的時候遞推出前綴 k 又有多少 border,記做 cnt[k]。對於 i 來講,num[i] = cnt[k] + 1(多一個 1 是由於 k 自己也是合法的)。

可是倍增是 log 的,而這道題顯然卡 log 作法。
咱們能夠考慮類比 kmp 的求解方法,在求前綴 i 的時候充分利用前綴 i-1 的信息。
具體到這一道題,有 fir[i] <= fir[i-1] + 1
那麼從 fir[i-1] 開始暴力跳 next 往回找第一個能夠拼接出前綴 i 的 border 的位置,這個位置 + 1 就是咱們要找的 fir[i]。
時間複雜度和 kmp 的證實方法同樣。

@accepted code@

#include <cstdio>
#include <cstring>
const int MAXN = 1000000;
const int MOD = int(1E9) + 7;
int f1[MAXN + 5], f2[MAXN + 5];
int num[MAXN + 5], cnt[MAXN + 5];
char S[MAXN + 5];
void solve() {
    scanf("%s", S);
    int lenS = strlen(S);
    f1[0] = cnt[0] = -1, f1[1] = 0;
    for(int i=2;i<=lenS;i++) {
        int j = f1[i - 1];
        while( j != -1 && S[j] != S[i-1] )
            j = f1[j];
        f1[i] = j + 1, cnt[i] = cnt[f1[i]] + 1;
    }
    f2[0] = -1, f2[1] = 0;
    for(int i=2;i<=lenS;i++) {
        int j = f2[i - 1];
        while( j != -1 && (S[j] != S[i-1] || 2*(j + 1) > i) )
            j = f1[j];
        f2[i] = j + 1, num[i] = cnt[f2[i]] + 1;
    }
    int ans = 1;
    for(int i=1;i<=lenS;i++)
        ans = 1LL*ans*(num[i] + 1)%MOD;
    printf("%d\n", ans);
}
int main() {
    int n; scanf("%d", &n);
    for(int i=1;i<=n;i++)
        solve();
}

@details@

P.S:本篇博客是供博主複習所用,因此有不少簡略的地方以及描述不清的地方。

相關文章
相關標籤/搜索