1. 定義
1.1 前綴 & 真前綴
前綴是指從串首開(kāi)始到某個(gè)位置 結(jié)束的一個(gè)特殊子串。字符串
的以
結(jié)尾的前綴表示為
真前綴指除了 本身的
的前綴。
1.2 后綴 & 真后綴
后綴是指從某個(gè)位置 開(kāi)始到整個(gè)串末尾結(jié)束的一個(gè)特殊子串。字符串
的從
開(kāi)頭的后綴表示為
真后綴指除了 本身的
的后綴。
1.3 前綴函數(shù)
給定一個(gè)長(zhǎng)度為 的字符串
,其前綴函數(shù)定義為一個(gè)長(zhǎng)度為
的數(shù)組
。其中
含義為:
- 如果子串
有相等的真前綴
和真后綴
,那么
為最大的相等的真前后綴長(zhǎng)度,即
- 如果子串
沒(méi)有相等的真前后綴,則
1.4 字符串的周期
對(duì)于字符串 和
,若
對(duì)于所有
成立,則稱(chēng)
是
的周期。
1.5 字符串的 border
對(duì)于字符串 和
,若
長(zhǎng)度為
的前綴和長(zhǎng)度為
的后綴相等,就稱(chēng)
長(zhǎng)度為
的前綴(后綴)是
的 border 。
【注】易知前綴函數(shù) 對(duì)應(yīng)的就是字符串
的最長(zhǎng) border 的長(zhǎng)度。
2. 性質(zhì)
如果字符串
有長(zhǎng)度為
的 border,則
是
的周期。
如果字符串
的前綴函數(shù)為
,
,則:
所有的 border 長(zhǎng)度為
。
所有的周期為
。
是
的最長(zhǎng) border 的長(zhǎng)度,
是
的最小周期。
3. 實(shí)現(xiàn)
根據(jù)前綴函數(shù)的定義我們可以發(fā)現(xiàn),相鄰的前綴函數(shù)值至多增加 1 ,故可以得到字符串 的前綴函數(shù)的計(jì)算公式:
-
。
- 如果
,則
- 如果
,令
。若
,則令
,直到
為止,則
【注】計(jì)算字符串的前綴函數(shù)的思想和 KMP 算法中計(jì)算字符串失配數(shù)組的思想非常相似。
4. 應(yīng)用
4.1 KMP
前綴函數(shù)可以用來(lái)實(shí)現(xiàn) KMP 算法,思路為:拼接模式串 和主串
,得到
,
為不在
和
中出現(xiàn)的字符。設(shè)
計(jì)算拼接后的字符串
的前綴函數(shù),當(dāng)出現(xiàn)
時(shí),說(shuō)明此時(shí)模式串匹配上了主串的子串
。
整個(gè)算法時(shí)間復(fù)雜度為 。
4.2 字符串周期 & border
根據(jù)上文中給出的性質(zhì),可以很容易求出字符串 的字符串周期 & border。假設(shè)
,則可以在
時(shí)間內(nèi)求出
的所有周期 & border。
4.3 統(tǒng)計(jì)每個(gè)前綴出現(xiàn)次數(shù)
- 統(tǒng)計(jì)字符串
的所有前綴子串在
中出現(xiàn)的次數(shù),
。
- 首先統(tǒng)計(jì)前綴數(shù)組值
,
表示字符串
最長(zhǎng)相等真前后綴長(zhǎng)度,即說(shuō)明前綴
在
中出現(xiàn)了 1 次(不包括前綴本身)。
- 前綴數(shù)組值統(tǒng)計(jì)后,只統(tǒng)計(jì)出了每個(gè)前綴作為某個(gè)字符串
的最長(zhǎng)真后綴的出現(xiàn)次數(shù),而沒(méi)有統(tǒng)計(jì)非最長(zhǎng)真后綴的出現(xiàn)次數(shù),故根據(jù)
數(shù)組的性質(zhì)統(tǒng)計(jì)非最長(zhǎng)真后綴的出現(xiàn)次數(shù)。
- 加上每個(gè)前綴本身 1 次。
ll ans[MAXN]; // 對(duì)應(yīng)長(zhǎng)度的前綴在字符串中出現(xiàn)的次數(shù)
void getAns(ll m) {
// ans[0] 沒(méi)有實(shí)際意義
for(ll i = 0; i < m; ++i) ++ans[pi[i]];
for(ll i = m-1; i; --i) ans[pi[i-1]] += ans[i];
for(ll i = 0; i <= m; ++i) ++ans[i];
}
- 統(tǒng)計(jì)字符串
的所有前綴子串在
中出現(xiàn)的次數(shù),
。拼接字符串
和
,使得
。
- 首先統(tǒng)計(jì)前綴數(shù)組值
,
表示字符串
最長(zhǎng)相等真前后綴長(zhǎng)度,即說(shuō)明前綴
在
中出現(xiàn)了 1 次(不包括前綴本身),易知最長(zhǎng)真前后綴都不會(huì)包含界定符
,故統(tǒng)計(jì)得到的只是字符串
中的。
- 前綴數(shù)組值統(tǒng)計(jì)后,只統(tǒng)計(jì)出了每個(gè)前綴作為某個(gè)字符串
的最長(zhǎng)真后綴的出現(xiàn)次數(shù),而沒(méi)有統(tǒng)計(jì)非最長(zhǎng)真后綴的出現(xiàn)次數(shù),故根據(jù)
數(shù)組的性質(zhì)統(tǒng)計(jì)非最長(zhǎng)真后綴的出現(xiàn)次數(shù)。
ll ans[MAXN]; // 對(duì)應(yīng)長(zhǎng)度的前綴在字符串中出現(xiàn)的次數(shù)
void getAns(ll m, ll n) {
// ans[0] 沒(méi)有實(shí)際意義
// 只統(tǒng)計(jì)字符串 t 中的
for(ll i = m+1; i < n+m+1; ++i) ++ans[pi[i]];
for(ll i = m; i; --i) ans[pi[i-1]] += ans[i];
}
4.4 不同子串?dāng)?shù)目
給定字符串 ,其長(zhǎng)度
,計(jì)算
中不同的子串的數(shù)目。
- 設(shè)字符串
的不同子串?dāng)?shù)目為
,則向
末尾添加一個(gè)字符后得到字符串
。顯然
的子串中可能會(huì)出現(xiàn)一些新的以
結(jié)尾的子串。
- 反轉(zhuǎn)字符串
得到字符串
,則問(wèn)題變成統(tǒng)計(jì)以
開(kāi)頭且未在
的其他地方出現(xiàn)的前綴數(shù)目。
- 設(shè)
的前綴函數(shù)的最大值為
,則最長(zhǎng)的出現(xiàn)在
其他地方的前綴長(zhǎng)度為
,故更短的前綴也一定出現(xiàn)了。
- 因此,字符串
新增一個(gè)末尾字符
后新出現(xiàn)的子串的數(shù)目為
。
【注】從頭部添加、頭部移除或尾部移除后計(jì)算不同子串的思想類(lèi)似。
4.5 字符串壓縮
- 給定字符串
,其長(zhǎng)度
,我們希望找到一個(gè)最短的字符串
,使得
為
的一份或多份拷貝的拼接表示。
- 顯然,我們只需要找到
的長(zhǎng)度即可,該問(wèn)題的答案即為長(zhǎng)度為該值的
的前綴。
根據(jù)上文的性質(zhì)可知,如果計(jì)算出 的前綴函數(shù)之后,
的最小周期為
。由字符串的周期的定義可知,最后字符串
刪去每段周期長(zhǎng)度的字符串后,剩余的最后一段字符串長(zhǎng)度不一定是
。故如果
,則
即是
的長(zhǎng)度,否則不存在一個(gè)有效的壓縮,即
的長(zhǎng)度為
。
5. 代碼
#include <bits/stdc++.h>
using namespace std;
// 前綴函數(shù)
struct PrefixFunction {
#ifndef _PREFIXFUNCTION_
#define ll int
#define MAXN 1000005
#endif
ll cnt; // 字符串的 border(或周期)個(gè)數(shù)
ll pi[MAXN]; // 前綴函數(shù)
ll border[MAXN]; // border 長(zhǎng)度數(shù)組(從大到?。? ll period[MAXN]; // 周期數(shù)組(從小到大)
PrefixFunction(): cnt(0) {}
// 計(jì)算前綴函數(shù)
void getPi(char *str, ll n) {
pi[0] = 0;
ll i = 1, j = pi[i-1];
while(i < n) {
if(str[i] == str[j]) {
pi[i++] = j++ + 1;
} else if(!j) {
pi[i++] = j;
} else {
j = pi[j-1];
}
}
}
// 計(jì)算所有 border 的長(zhǎng)度
void getBorder(ll n) {
ll count = 0;
ll j = pi[n-1];
while(j) {
border[count++] = j;
j = pi[j-1];
}
cnt = count;
}
// 計(jì)算所有周期
void getPeriod(ll n) {
ll count = 0;
ll j = pi[n-1];
while(j) {
period[count++] = n - j;
j = pi[j-1];
}
cnt = count;
}
};