題意:
給你一個串,問你他的每個前綴的最小重複單元,其中單元是可以重疊的,最後按順序輸出即可。比如樣例中abaabaa的最小重複單元爲abaa,所以相應輸出爲4。
樣例:
input : abaabaababa
output:1 2 3 4 5 3 4 5 3 10 3
kmp過程就不用多說了,現在我們利用next數組的性質來對問題進行求解。
我們首先用一個ans[maxn]數組來記錄最後的答案,且我們的字符串下標從0開始,顯然,我們ans[i]的最大值爲i+1,我們因此也用此值對其進行初始化。
現在我們考慮什麼情況下ans[i]可以得到更小的,更小他能達到多小。
其實這個顯然可以知道第i位的ans值只能從ans[next[i]]那裏獲得,這個就可以根據next數組的性質想一想就明白了,若存在更短的,到i的後面的這段就不成立了。
然後我們考慮i和next[i]位置的兩種情況:
第一種情況:i - next[i] ≤ next[i]
此時顯然就可以利用ans[next[i]]進行更新了,如果可以形成前面這段,那麼一定可以形成後面那段。
第二種情況:i - next[i] > next[i]
這種情況就是此題的難點所在了,乍看之下似乎這種情況下只能放棄用ans[next[i]]來更新ans[i]了,其實不然!!!
樣例就給我們了很好的反例,因爲樣例最後一位的答案是3!
我們用dp[k]來記錄最後ans是k的最大的下標,我們假設cnt = ans[next[i]],即在next[i]處的答案,然後如圖:
一個比較顯而易見的是cnt ≤ next[i]是肯定成立的,而此種假設下我們假設i - next[i] ≤ next[i],現在決定最終成敗的就只剩下dp[cnt]的具體位置了!!!
我們證明 i - dp[cnt] ≤ cnt 時的情況必然可以用cnt來更新ans[i]。
此時狀況完全如上圖所示,此時在dp[cnt]前已經得知必可由長度爲cnt的串來產生,而這cnt長得串同時也肯定是從0到next[i]中長度爲cnt的後綴。那麼根據next數組的性質,這段串與i-cnt ~ i這段是相同的。我們又假設了 i - cnt ≤ dp[cnt] ,因此我們的i-cnt ~ i這段必然可由與形成dp[cnt]長度相同的串來產生,同時這也是其所有可能的最小答案。
最後當next[i] = -1 的時候就沒什麼說的了,顯然上面說的這些都沒用了,直接就賦值給 ans[i] = i + 1,再更新一下 dp[ans[i]] = i 就可以了。
在此表達對此神作法的膜拜之情!
本弱渣的代碼如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
char word[250010];
int next[250010], ans[250010], dp[250010];
int main() {
freopen("cover.in", "r", stdin);
freopen("cover.out", "w", stdout);
scanf("%s", word);
int len = strlen(word), k = -1;
next[0] = -1;
for (int i = 1; i < len; i++) { // KMP構造next數組過程
while (k != -1 && word[i] != word[k + 1]) k = next[k];
if (word[i] == word[k + 1]) k++;
next[i] = k;
}
for (int i = 0; i < len; i++) {
ans[i] = i + 1; //首先賦值最大的可能值i+1
if (next[i] != -1) {
int cnt = ans[next[i]];
if (i - next[i] <= next[i] || i - dp[cnt] <= cnt) {
ans[i] = cnt;
}
}
dp[ans[i]] = i; //用ans[i]去更新dp[ans[i]]
}
for (int i = 0; i < len - 1; i++) printf("%d ", ans[i]); printf("%d\n", ans[len - 1]);
return 0;
}