Strings
227 高翊恩
關於講師
- OJ id: pring、pringDeSu
- 喜歡壓一行
- 左閉右開
會提到的東西
- 字串匹配
- 數回文
- サ及LCP
關於string

C++ STL string
- unsigned int size()
- unsigned int find(char)
- string substr(from, length) (也會寫成sl…r)
#include <bits/stdc++.h>
using namespace std;
int main() {
string s = "abcdefg";
cout << s.size() << endl; // 7
cout << s.find('f') << endl; // 5
cout << s.find('z') << endl; // 4294967295
cout << s.substr(2, 3) << endl; // "cde"
return 0;
}
事先定義
- s的第i個前綴:s.substr(0, i) (長度為i)
- 我喜歡叫它Ps[i]
- s的第i個後綴:s.substr(i, s.size() - i) (從s[i]開始)
#include <bits/stdc++.h>
using namespace std;
string Prefix(string s, string i) {
return s.substr(0, i);
}
string Suffix(string s, string i) {
return s.substr(i, s.size() - i);
}
int main() {
string s = "abcdefg";
cout << Prefix(s, 3) << endl; // "abc"
cout << Suffix(s, 4) << endl; // "defg"
cout << Prefix(s, 0) << endl; // ""
return 0;
}
事先定義
- 回文
- Palindrome
- 我簡稱PD
#include <bits/stdc++.h>
using namespace std;
bool isPD(string s) {
int n = s.size();
for (int i = 0; i < (n >> 1); i++) if (s[i] != s[n - i + 1]) return false;
return true;
}
事先定義
- 字典序
- lexicographical order
- 從最左邊開始比
- (char) '\0' = (int) 0
- bool operator>(string, string)
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<string> v;
for (auto &i : v) cin >> i;
sort(v.begin(), v.end());
for (auto &i : v) cout << i << endl;
return 0;
}
Trie
Trie
- 把一堆字串用樹狀結構存起來
- 沒了
aa
aba
bb
bba
試試看:
Trie
- 把一堆字串用樹狀結構存起來
- 沒了
aa
aba
bb
bba
試試看:
struct TrieNode {
int C;
vector<TrieNode*> child;
TrieNode() {
C = 0;
child = vector<TrieNode*>(26, nullptr);
}
void push(string s) {
TrieNode *now = this;
for (auto &i : s) {
if (now -> child[i - 'a'] == nullptr) now -> child[i - 'a'] = new TrieNode();
now = now -> child[i - 'a'];
}
now -> C++;
}
};
Code
其實滿單純的
- 給一個大字串s和一坨小字串t[]
- 問有多少種組合方式可以只用t中的字串組合出s(可重複使用)
- ∣s∣≤5000
- t.size()≤105,∑∣ti∣<106
DP解
- 令dp[i]=Ps[i]的組合方法
- dpi+1=j∈t∑[(si+1−∣j∣…i+1==j)×dpi+1−j]
- 轉移太久了...
高級一點的DP解
- 令dp[i]=Ps[i]的組合方法
- dpi+1=j∈t∑[(si+1−j…i+1==j)×dpi+1−j]
- 上面這個可以優化成對所有結尾在i+1的子字串,看有沒有在t裡面
- 高級技巧:反著做
- 把t弄成一個set
高級一點的DP解
- 令dp[i]=Ps[i]的組合方法
- dpi+1=j∈t∑[(si+1−j…i+1==j)×dpi+1−j]
- 上面這個可以優化成對所有結尾在i+1的子字串,看有沒有在t裡面
- 高級技巧:反著做
- 把t弄成一個set
O(n2logn)
來把log壓掉
用空間換取時間
來把log壓掉
用空間換取時間
dp[] | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
val | 1 | 0 | 1 | 0 |
a
a
b
b
c
dp[] | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
val | 1 | 0 | 1 | 0 |
a
a
b
b
c
dp[] | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
val | 1 | 0 | 1 | 0 |
a
a
b
b
c
dp[] | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
val | 1 | 0 | 1 | 0 |
a
a
b
b
c
dp[] | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
val | 1 | 0 | 1 | 0 |
a
a
b
b
c
dp[] | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
val | 1 | 0 | 1 | 0 |
a
a
b
b
c
dp[] | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
val | 1 | 0 | 1 | 0 |
a
a
b
b
c
dp[] | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
val | 1 | 0 | 1 | 0 | 2 |
a
a
b
b
c
所以應該知道Trie可以做什麼了
Hash
把字串當數字

Assembly
16進位制
Σ={0…9,A…F}
轉換方式:
8E5C16=3644410
26進位制
Σ={a…z}
轉換方式:
pring26=715918410
問題1
a→0
pring26=aaaaapring26
27進位制
Σ={a…z}
{a,b,…,z}→{1,2,…,26}
轉換方式:
pring27=886429610
問題2
數字太大啦!!
所以送它一個%
27進位制
Σ={a…z}
{a,b,…,z}→{1,2,…,26}
最後要%(1e9+7)
轉換方式:
pring27=886429610
Hash有兩種
正著做:
Hash(s)=(s0p0+s1p1+s2p2+⋯+s∣s∣−1p∣s∣−1)%M
反著做:
hsaH(s)=(s0p∣s∣−1+s1p∣s∣−2+s2p∣s∣−3+⋯+s∣s∣−1p0)%M
Hash 大重點
- 當兩個字串Hash後的數字相同,則視為兩個字串相同
- 數字要選好,不然會撞
- 撞了就多選幾個質數和進位方法多做幾次
- 剩餘定理
Hash做字串匹配
s=abbabbab
t=abbab
Hash(t)=1084078
[from, to) | substring of s | Hash() |
---|---|---|
[0, 5) | abbab | 1084078 |
[1, 6) | bbabb | 1103033 |
[2, 7) | babba | 572294 |
[3, 8) | abbab | 1084078 |
可是直接算Hash太慢了
可不可以利用Hash(s.substr(0,i)),只用O(1)求出Hash(s.substr(1,i))?
遞推法
s0s1s2s3s4⟶h
s1s2s3s4⟶h−s0p∣t∣
s1s2s3s4_⟶(h−s0p∣t∣)×27
s1s2s3s4s5⟶(h−s0p∣t∣)×27+s5
記得有模運算
這裡用的是hsaH
前綴和
開一個陣列Pref記錄Hash(Ps[i])
則可以用O(1)求出(i=l∑r−1sipi)%M
可是我們要的是(i=l∑r−1sipi−l)%M
模運算
int mod;
int PLUS(int x, int y) {
return (x + y) % mod;
}
int MINUS(int x, int y) {
return (x - (y % mod) + mod) % mod;
}
int TIMES(int x, int y) {
return (x * y) % mod;
}
模逆元
- P÷Q=P×Q−1
Q×Q−1=1
⇒Q×Q−1≡1(modM)
所以想辦法找到Q−1就好了
怎麼找?
費馬小定理
ap≡a(modp)
a<p⇒ap−1≡1(modp)
⇒ap−2≡a−1(modp)
int modPow(int a, int x) {
int ans = 1;
for (int i = 1 << 30; i > 0; i >>= 1) {
ans = ans * ans % mod;
if (i & x) ans = ans * a % mod;
}
return ans;
}
int DIVIDE(int x, int y) {
return x * modPow(y, mod - 2) % mod;
}
回到Hash
int mod;
int modPow(int a, int x) {
int ans = 1;
for (int i = 1 << 30; i > 0; i >>= 1) {
ans = ans * ans % mod;
if (i & x) ans = ans * a % mod;
}
return ans;
}
struct Hash {
int p;
vector<int> pref;
Hash(string s, int _p) {
p = _p;
int n = s.size();
pref.resize(n + 1);
int mul = 1;
pref[0] = 0;
for (int i = 0; i < n; i++) {
pref[i + 1] = (pref[i] + (s[i] - 'a' + 1) * mul) % mod;
mul = mul * p % mod;
}
}
int query(int from, int len) {
int ans = (pref[from + len] - pref[from] + mod) % mod;
return ans * modpow(modpow(p, mod - 2), from) % mod;
}
};
題單
- CSES String Matching (裸題)
- TIOJ 1306 (也是裸題)
- TIOJ 1321
- CSES Palindrome Quries
KMP
共同前後綴
Prefix which is also Suffix
我喜歡叫它CPS (Common Prefix and Suffix)
共同前後綴
- 是前綴也是後綴的子字串
- 所有滿足條件的子字串,由長到短排成一列
- 定義s.CPS(i)為這一列中的第i個
字串 | CPS(0) | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|---|
"abcabca" | "abcabca" | "abca" | "a" | "" |
"abcde" | "abcde" | "" | --- | --- |
"zzz" | "zzz" | "zz" | "z" | "" |
共同前後綴
- 也可以用數字存
- 第k個前綴與第k個後綴相同
字串 | CPS(0) | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|---|
"abcabca" | 7 | 4 | 1 | 0 |
"abcde" | 5 | 0 | -1 | -1 |
"zzz" | 3 | 2 | 1 | 0 |
對所有前綴存
s=abbabbab
s的前綴 | CPS(0) | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|---|
"" | "" | --- | --- | --- |
"a" | "a" | "" | --- | --- |
"ab" | "ab" | "" | --- | --- |
"abb" | "abb" | "" | --- | --- |
"abba" | "abba" | "a" | "" | --- |
"abbab" | "abbab" | "ab" | "" | --- |
"abbabb" | "abbabb" | "abb" | "" | --- |
"abbabba" | "abbabba" | "abba" | "a" | "" |
"abbabbab" | "abbabbab" | "abbab" | "ab" | "" |
對所有前綴存
s=abbabbab
Ps[i] | CPS(0) | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|---|
0 | 0 | -1 | -1 | -1 |
1 | 1 | 0 | -1 | -1 |
2 | 2 | 0 | -1 | -1 |
3 | 3 | 0 | -1 | -1 |
4 | 4 | 1 | 0 | -1 |
5 | 5 | 2 | 0 | -1 |
6 | 6 | 3 | 0 | -1 |
7 | 7 | 4 | 1 | 0 |
8 | 8 | 5 | 2 | 0 |
表格減肥
減肥第一步
Ps[i] | CPS(0) | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|---|
0 | 0 | -1 | -1 | -1 |
1 | 1 | 0 | -1 | -1 |
2 | 2 | 0 | -1 | -1 |
3 | 3 | 0 | -1 | -1 |
4 | 4 | 1 | 0 | -1 |
5 | 5 | 2 | 0 | -1 |
6 | 6 | 3 | 0 | -1 |
7 | 7 | 4 | 1 | 0 |
8 | 8 | 5 | 2 | 0 |
↑ 這一行沒用
減肥第二步
Ps[i] | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|
0 | -1 | -1 | -1 |
1 | 0 | -1 | -1 |
2 | 0 | -1 | -1 |
3 | 0 | -1 | -1 |
4 | 1 | 0 | -1 |
5 | 2 | 0 | -1 |
6 | 3 | 0 | -1 |
7 | 4 | 1 | 0 |
8 | 5 | 2 | 0 |
觀察性質
減肥第二步
Ps[i] | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|
0 | -1 | -1 | -1 |
1 | 0 | -1 | -1 |
2 | 0 | -1 | -1 |
3 | 0 | -1 | -1 |
4 | 1 | 0 | -1 |
5 | 2 | 0 | -1 |
6 | 3 | 0 | -1 |
7 | 4 | 1 | 0 |
8 | 5 | 2 | 0 |
觀察性質
減肥第二步
Ps[i] | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|
0 | -1 | -1 | -1 |
1 | 0 | -1 | -1 |
2 | 0 | -1 | -1 |
3 | 0 | -1 | -1 |
4 | 1 | 0 | -1 |
5 | 2 | 0 | -1 |
6 | 3 | 0 | -1 |
7 | 4 | 1 | 0 |
8 | 5 | 2 | 0 |
觀察性質
減肥第二步
Ps[i] | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|
0 | -1 | -1 | -1 |
1 | 0 | -1 | -1 |
2 | 0 | -1 | -1 |
3 | 0 | -1 | -1 |
4 | 1 | 0 | -1 |
5 | 2 | 0 | -1 |
6 | 3 | 0 | -1 |
7 | 4 | 1 | 0 |
8 | 5 | 2 | 0 |
觀察性質
證明
s.CPS(2)=s.CPS(1).CPS(1)
abbabba → abba → a
abba → a
abbabba
abbabba
abbabba
abba
abbabba
abba
abbabba
abba
abbabba
abba
a
abbabba
abba
a
99% Completed!
我們可以推得:
若t∈s.CPS
則t.CPS⊂s.CPS
次序問題
-
根據定義:s.CPS(1)為「除了s本身的最長CPS」
- 令t=s.CPS(1),則t.CPS(1)就是「除了t本身的最長CPS」
- 根據剛剛推過的,t.CPS(1)∈s.CPS,而且是「除了s及t之外的最長CPS」
- 那就是s.CPS(2)
次序問題
再認真想一下,我們可以發現
s.CPS(a+b)=s.CPS(a).CPS(b)
阿這跟減肥有什麼關係
Ps[i] | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|
0 | -1 | -1 | -1 |
1 | 0 | -1 | -1 |
2 | 0 | -1 | -1 |
3 | 0 | -1 | -1 |
4 | 1 | 0 | -1 |
5 | 2 | 0 | -1 |
6 | 3 | 0 | -1 |
7 | 4 | 1 | 0 |
8 | 5 | 2 | 0 |
阿這跟減肥有什麼關係
Ps[i] | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|
0 | -1 | -1 | -1 |
1 | 0 | -1 | -1 |
2 | 0 | -1 | -1 |
3 | 0 | -1 | -1 |
4 | 1 | 0 | -1 |
5 | 2 | 0 | -1 |
6 | 3 | 0 | -1 |
7 | 4 | 1 | 0 |
8 | 5 | 2 | 0 |
阿這跟減肥有什麼關係
Ps[i] | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|
0 | -1 | -1 | -1 |
1 | 0 | -1 | -1 |
2 | 0 | -1 | -1 |
3 | 0 | -1 | -1 |
4 | 1 | 0 | -1 |
5 | 2 | 0 | -1 |
6 | 3 | 0 | -1 |
7 | 4 | 1 | 0 |
8 | 5 | 2 | 0 |
阿這跟減肥有什麼關係
Ps[i] | CPS(1) | CPS(2) | CPS(3) |
---|---|---|---|
0 | -1 | -1 | -1 |
1 | 0 | -1 | -1 |
2 | 0 | -1 | -1 |
3 | 0 | -1 | -1 |
4 | 1 | 0 | -1 |
5 | 2 | 0 | -1 |
6 | 3 | 0 | -1 |
7 | 4 | 1 | 0 |
8 | 5 | 2 | 0 |
令t為Ps的其中一個元素
則有t.CPS⊆Ps
如果我們對所有s的前綴,記錄其CPS(1)
則t.CPS(2)=t.CPS(1).CPS(1)
很容易就可以得到t.CPS(2)
又t.CPS(3)=t.CPS(2).CPS(1)
很容易就可以得到t.CPS(3)
...
所以其實存第1行就可以了
Ps[i] | CPS(1) |
---|---|
0 | -1 |
1 | 0 |
2 | 0 |
3 | 0 |
4 | 1 |
5 | 2 |
6 | 3 |
7 | 4 |
8 | 5 |
當我們要找s的第7個前綴的所有共同前後綴時
Ps[7].CPS(1)
Ps[7].CPS(2)
Ps[7].CPS(3)
終於…
- 壓成一個一維陣列
- 利用這個陣列我們可以求出所有前綴的任何CPS
- 定義πs[i]:Ps[i]的「次長共同前後綴」(CPS(1))
s=abbabbab
πs={−1,0,0,0,1,2,3,4,5}
暫停一下
找出π陣列
vector<int> Pi(string s) {
int n = s.size();
vector<int> result(n + 1);
result[0] = -1;
for (int i = 1; i < n; i++) {
for (int j = i - 1; j >= 0; j--) {
if (s.substr(0, j) == s.substr(i - j + 1, j)) {
result[i] = j;
break;
}
}
}
return result;
}
複雜度:O(n3)
超級慢
DP一下
初始條件:
π[0]=−1,π[1]=0
觀察性質
Ps[πs[i+1]−1]∈Ps[i].CPS
$$$$$$$$$$$$$$$$$$ $…
Ps[i+1]
假設我們要找πs[i+1]
所以就有找法了
找πs[i+1]時
令j=1,2,…
如果Ps[i].CPS(j)的「下一個字元」==s[i+1]
則πs[i+1]=Ps[i].CPS(j)+1
$$$$$$$?$$$$$$$$$$?…
Ps[i+1]
s=abbacabbab…
πs={−1,0,0,0,1,0,1,2,3,4,?}
abbacabba b
s=abbacabbab…
πs={−1,0,0,0,1,0,1,2,3,4,?}
abbacabba b
Ps[9].CPS(1)=πs[9]=4
s=abbacabbab…
πs={−1,0,0,0,1,0,1,2,3,4,?}
abbacabba b
Ps[9].CPS(1)=πs[9]=4
s=abbacabbab…
πs={−1,0,0,0,1,0,1,2,3,4,?}
abbacabba b
Ps[9].CPS(2)=πs[πs[9]]=1
s=abbacabbab…
πs={−1,0,0,0,1,0,1,2,3,4,?}
abbacabba b
Ps[9].CPS(2)=πs[πs[9]]=1
s=abbacabbab…
πs={−1,0,0,0,1,0,1,2,3,4,?}
abbacabba b
πs[9+1]=1+1=2
Code
現場打!
KMP應用
匹配字串
abbaabbab
abbaabbab
abbab
abbab
abbab
abbab
abbab
拿t往右推
t每次往右推一格
abbaabbab
abbab
abbab
abbab
abbab
abbab
拿t往右推
可不可以預知這兩個「壯志未酬身先死」?
t每次往右推一格
$$$$$$$$$$$$$$$$$$$ $
t[j]
$$$$$$$$$$$$$$ $
t[j−x]
證明當s[i]=t[j]時候:
- 選某些x∈S的情況下可以確保t可以完美地配到t[j−x]
- 其他狀況的x都一定會在配到t[j−x]前就爛掉
那我們這個操作就可以將t向右推S中最小的
…$$$$$$$$$$$$$$$$$$$ $…
t往右推了x格
$$$$$$$$$$$$$$$$$$$ $
t[j]
$$$$$$$$$$$$$$ $
t[j−x]
證明當s[i]=t[j]時候:
- 選某些x∈S的情況下可以確保t可以完美地配到t[j−x]
- 其他狀況的x都一定會在配到t[j−x]前就爛掉
那我們這個操作就可以將t向右推S中最小的
…$$$$$$$$$$$$$$$$$$$ $…
t往右推了x格
好啦其實就是CPS啦
$$$$$$$$$$$$$$$$$$$ $
t[j]
$$$$$$$$$$$$$$ $
t[j−x]
證明當s[i]=t[j]時候:
- 選某些x∈S的情況下可以確保t可以完美地配到t[j−x]
- 其他狀況的x都一定會在配到t[j−x]前就爛掉
那我們這個操作就可以將t向右推S中最小的
…$$$$$$$$$$$$$$$$$$$ $…
t往右推了x格
好啦其實就是CPS啦
$$$$$$$$$$$$$$$$$$$ $
t[j]
$$$$$$$$$$$$$$ $
t[j−x]
假設j−x∈/Pt[j].CPS
且成功配到藍色格子
…$$$$$$$$$$$$$$$$$$$ $…
$$$$$$$$$$$$$$$$$$$ $
t[j]
$$$$$$$$$$$$$$ $
假設j−x∈/Pt[j].CPS
且成功配到藍色格子
…$$$$$$$$$$$$$$$$$$$ $…
t[j−x]
$$$$$$$$$$$$$$$$$$$ $
t[j]
$$$$$$$$$$$$$$ $
假設j−x∈/Pt[j].CPS
且成功配到藍色格子
…$$$$$$$$$$$$$$$$$$$ $…
則j−x∈Pt[j].CPS
⇒只有推x格的人可以活下來
t[j−x]
所以發現錯誤的時候
就找現在配到的字串的CPS(1) (π)
然後從剛剛錯的地方繼續
abbaabbab
abbab
abbab
abbab
abbabbab
適當地加入$
abbab$
abbab
Code
vector<int> StringMatching(string s, string t) {
int n = s.size(), m = t.size();
vector<int> pi = Pi(t), ans;
s.push_back('^');
t.push_back('$');
for (int i = 0, j = 0; i < n + 1; i++, j++) {
if (j == m) ans.push_back(i - m);
while (j >= 0 && s[i] != t[j]) j = pi[j];
}
return ans;
}
另一種想法
只要把t,$,s連起來
然後做π
然後數$後面哪些的π值是∣t∣
vector<int> stringMatching(string s, string t) {
int m = t.size();
vector<int> v;
vector<int> pi = Pi(t + "$" + s);
for (int i = 0; i < pi.size(); i++) {
if (pi[i] == m) v.push_back(i - m - m);
}
return v;
}
為什麼不先講這個…
Prefix Automaton
前綴自動機
…abbaa…
abbab
abbab
abbab
優化再優化!
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
t=abbab
對t開一張PA表:
- 目前要配對t的第i個字元(配完i個字元)
- s的下一個字元為j
- 配完這兩個字元後t可能會被連續地推動
- 對到s[j+1]的會是t[PA[i][j]]
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
abbaabbab
i | 'a' | 'b' |
---|---|---|
0 | 1 | 0 |
1 | 1 | 2 |
2 | 1 | 3 |
3 | 4 | 0 |
4 | 1 | 5 |
5 | 1 | 3 |
abbab
Code
vector<vector<int>> PrefixAutomaton(string s) {
int n = s.size();
vector<vector<int>> dp(n + 1, vector<int>(26, 0));
vector<int> pi = Pi(s);
s += '$';
dp[0][s[0] - 'a'] = 1;
for (int i = 1; i <= n; i++) for (int j = i; j >= 0; j = pi[j]) if (j < n) dp[i][s[j] - 'a'] = max(dp[i][s[j] - 'a'], j + 1);
return dp;
}
vector<int> StringMatching(string s, string t) {
int n = s.size(), m = t.size();
auto PA = PrefixAutomaton(t);
vector<int> ans;
for (int i = 0, j = 0; i < n; i++) {
j = PA[j][s[i] - 'a'];
if (j == m) ans.push_back(i - m + 1);
}
return ans;
}
建表:O(n)
配對:O(1)/每個字元
阿字串匹配不是直接用π做的
幹嘛還要學PA
Compressed String
給定三字串s0,s1,t和一整數n
令si+1=5×si+6×si−1,i∈Z+
求在sn裡面可以找到幾個t
Compressed String
給定三字串s0,s1,t和一整數n
令si+1=5×si+6×si−1,i∈Z+
求在sn裡面可以找到幾個t
顯然地
∣sn∣=71{[6×(−1)n+6n]∣s0∣+[6n−(−1)n]∣s1∣}
直接用π會炸
直接用PA也會炸
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | 2 |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | 2 |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | 2 |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | 2 |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | (+1) |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | 2 |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | (+1) |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | 2 |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | (+1) |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | 2 |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | (+1) |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | 2 |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | (+1) |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | 2 |
1 | 1 | 2 | |
2 | 1 | 3 | |
3 | 4 | 0 | |
4 | 1 | 5 | 2 (+1) |
5 | 1 | 3 |
t=abbab
那我們就把配對給壓縮吧
i | 'a' | 'b' | "bab" |
---|---|---|---|
0 | 1 | 0 | 2 |
1 | 1 | 2 | 2 |
2 | 1 | 3 | 5 (+1) |
3 | 4 | 0 | 2 |
4 | 1 | 5 | 2 (+1) |
5 | 1 | 3 | 5 (+1) |
t=abbab
PA的好處
- 高級KMP
- 把字串當作字元在做
- 一次配一個字串
題單
Aho-Corasick Automaton
Automaton
一台自動機=⟨Q,Σ,δ,q0,F⟩
=⟨狀態,字母表,轉移函式,初始狀態,終止狀態⟩
Q=圈圈們
Σ={a,b}
δ=箭頭們
q0=橘色圈圈
F=綠色圈圈們
走走看:abbaabbab
真 ● PA
t=abbab
走走看:abbaabbab
還記得Trie嗎
在Trie上做PA
δ(q,c)=
在q所代表的字串+c中,
有出現在樹上的最長後綴
在Trie上做PA
δ(q,c)=
在q所代表的字串+c中,
有出現在樹上的最長後綴
在Trie上做PA
δ(q,c)=
在q所代表的字串+c中,
有出現在樹上的最長後綴
在Trie上做PA
δ(q,c)=
在q所代表的字串+c中,
有出現在樹上的最長後綴
走走看:abbaabbab
但是這樣太久了
要換個做法
q的樹上後綴
- 有在樹上的後綴
- 由長排到短
- Suffix in Trie
- 我喜歡叫它SIT
SIT性質
- qi.SIT(0)=qi
- qi.SIT(2)=qi.SIT(1).SIT(1)
- 所以我們對所有節點做SIT(1)就可以找到所有節點的SIT(n)
SIT性質
- qi.SIT(0)=qi
- qi.SIT(2)=qi.SIT(1).SIT(1)
- 所以我們對所有節點做SIT(1)就可以找到所有節點的SIT(n)
- 我們叫它Suffix Link
建立Suffix Link
初始條件:
q[0].SIT(1)=nullptr
q[i].SIT(1)=q[0]
∀q[i]∈depth(1)
$$$$$$$$$ $
藍色∈Trie
假設紅色為qi.SIT(1)
則紅色∈Trie
則綠色∈Trie
則綠色∈藍色的SIT
qi
qi
所以就有找法了
找q[i].SIT(1)時
令j=1,2,…
如果q[i].father.SIT(j)有辦法走q[i].lastChar
則q[i].SIT(1)=q[i].father.SIT(j).next[q[i].lastChar]
$$$$$$$$$ $
嚇你的
嚇你的
嚇你的
嚇你的
如果他有辦法往a走的話
那個a就會是SIT
但它顯然沒有
嚇你的
嚇你的
好耶
範例
做它的SL
範例
範例
範例
範例
好耶
注意事項
- SL必定往上指
- 一定要先做完之前每代的所有SL才能做這個點
- BFS
阿這樣子要怎麼轉移
δ(qi,c)
- now=qi
- 如果now可以走c,則δ(qi,c)=now.next[c]
- 要不然就now=now.SL
- 做到now從root噴出去為止
走走看:abbaabbab
99% completed!
在abbabbab中匹配下列字串:
abbab
bab
ba
99% completed!
在abbabbab中匹配下列字串:
abbab
bab
ba
99% completed!
在abbabbab中匹配下列字串:
abbab
bab
ba
99% completed!
在abbabbab中匹配下列字串:
abbab
bab
ba
為什麼配不到bab和ba?
99% completed!
- 踩上一個點時,該點代表snow.SIT(1)
- 但如果snow.SIT(k)∈F,那它應該也要被配到
- 走一遍SL路徑
- 相當於是所有的Answer in Trie
99% completed!
- 踩上一個點時,該點代表snow.SIT(1)
- 但如果snow.SIT(k)∈F,那它應該也要被配到
- 走一遍SL路徑
- 相當於是要找到所有的Answer in Trie
- 我喜歡叫它AIT
99% completed!
- 跑一遍SL找AIT有點浪費時間…
- 壓縮SL的路徑,對每一個點q都存一條指向q′∣q′∈q.SIT∪F且q′為最長
- 我們叫它Answer Link
100% completed!
- 走走看:abbabbab
什麼時候生AIT?
- 跟著SIT一起做
好耶
看扣囉
struct TrieNode {
int id;
char lastChar;
TrieNode *next[26];
TrieNode *SL;
TrieNode *AL;
TrieNode(TrieNode *parent = nullptr, char _lastChar = '\0') {
id = -1;
lastChar = _lastChar;
fill(next, next + 26, nullptr);
SL = parent;
AL = nullptr;
}
void push(string s, int _id) {
TrieNode *now = this;
for (auto &i : s) {
if (now -> next[i - 'a'] == nullptr) now -> next[i - 'a'] = new TrieNode(now, i);
now = now -> next[i - 'a'];
}
now -> id = _id;
}
void construct() {
queue<TrieNode*> q;
for (auto &i : next) {
if (i == nullptr) continue;
q.push(i);
}
while (q.size()) {
TrieNode *now = q.front();
q.pop();
for (auto &i : now -> next) {
if (i == nullptr) continue;
q.push(i);
}
TrieNode *back = now -> SL -> SL;
while (back && back -> next[now -> lastChar - 'a'] == nullptr) back = back -> SL;
if (back) now -> SL = back -> next[now -> lastChar - 'a'];
else now -> SL = this;
if (now -> SL -> id != -1) now -> AL = now -> SL;
else now -> AL = now -> SL -> AL;
}
}
};
struct AC {
vector<string> s;
TrieNode *root;
AC(vector<string> _s) {
s = _s;
root = new TrieNode();
}
void push(string _s) {
s.push_back(_s);
}
void construct() {
int n = s.size();
for (int i = 0; i < n; i++) root -> push(s[i], i);
root -> construct();
}
vector<int> stringMatching(string _s) {
int n = s.size();
vector<int> ans(n);
TrieNode *now = root;
for (auto &i : _s) {
while (now && now -> next[i - 'a'] == nullptr) now = now -> SL;
if (now) now = now -> next[i - 'a'];
else now = root;
if (now -> id != -1) ans[now -> id]++;
TrieNode *back = now -> AL;
while (back) {
ans[back -> id]++;
back = back -> AL;
}
}
return ans;
}
};
題單
Gusfield's Algorithm
Gusfield's Algorithm

zs[i]
- r<∣s∣−iargmax(Ps[r]==si...i+r)
- 滿足Ps[r]==si…i+r中最大的r
- 說穿了就是從s[i]和s[0]開始最多能夠往右配幾個
s=abbabbab
zs={_,0,0,5,0,0,2,0}
字串匹配
對t+"$"+s做z
就好了
所以要怎麼生z
zzz
$$$$$$A$$$$$$$$$B$$$…
令l=jargmax(j+zs[j])
現在已經配到最右邊的那個人
我們現在要找zs[i]
根據i的位置,可以分成2種情況:
l
zs[l]
l+zs[l]
所以要怎麼生z
$$$$$$A$$$$$$$$$B$$$…
- i<l+zs[l]
l
zs[l]
l+zs[l]
這個時候我們令i′=i−l
即Prefix中對應到的i
i
所以要怎麼生z
$$$$$$A$$$$$$$$$B$$$…
- i<l+zs[l]
l
zs[l]
l+zs[l]
這個時候我們令i′=i−l
即Prefix中對應到的i
i
i′
所以要怎麼生z
$$C$$DA$$$$$$$$$B$$$…
1-1. i′+zs[i′]<zs[l]
l
zs[l]
l+zs[l]
i
i′
所以要怎麼生z
$$C$$DA$$$$$C$$DB$$$…
1-1. i′+zs[i′]<zs[l]
l
zs[l]
l+zs[l]
i
i′
所以要怎麼生z
$$C$$DA$$$$$C$$DB$$$…
1-1. i′+zs[i′]<zs[l]
l
zs[l]
l+zs[l]
i
i′
⇒zs[i]=zs[i′]
所以要怎麼生z
$$$C$$A$$$$$$$$$B$$$…
1-2. i′+zs[i′]=zs[l]
l
zs[l]
l+zs[l]
i
i′
所以要怎麼生z
$$$C$$A$$$$$$C$$B$$$…
1-2. i′+zs[i′]=zs[l]
l
zs[l]
l+zs[l]
i
i′
所以要怎麼生z
$$$C$$A$$$$$$C$$B$$$…
1-2. i′+zs[i′]=zs[l]
l
zs[l]
l+zs[l]
i
i′
A=B
C=B
A=C?
所以要怎麼生z
$$$C$$A$$$$$$C$$B$$$…
1-2. i′+zs[i′]=zs[l]
l
zs[l]
l+zs[l]
i
i′
所以這個時候
只能確定zs[i]≥zs[i′]
那就繼續配下去
所以要怎麼生z
$$$$C$AD$$$$$$$$B$$$…
1-3. i′+zs[i′]>zs[l]
l
zs[l]
l+zs[l]
i
i′
所以要怎麼生z
$$$$C$AD$$$$$$C$B$$$…
1-3. i′+zs[i′]>zs[l]
l
zs[l]
l+zs[l]
i
i′
所以要怎麼生z
$$$$C$AD$$$$$$C$B$$$…
1-3. i′+zs[i′]>zs[l]
l
zs[l]
l+zs[l]
i
i′
A=B
所以要怎麼生z
$$$$C$AD$$$$$$C$B$$$…
1-3. i′+zs[i′]>zs[l]
l
zs[l]
l+zs[l]
i
i′
⇒zs[i]=zs[l]−i′
所以要怎麼生z
$$$$$$A$$$$$$$$$B$$$…
2. i≥l+zs[l]
l
zs[l]
l+zs[l]
i
所以要怎麼生z
$$$$$$A$$$$$$$$$B$$$…
l
zs[l]
l+zs[l]
i
沒料
只好從頭配了
2. i≥l+zs[l]
Code
vector<int> Z(string s) {
int n = s.size();
vector<int> z(n);
z[0] = 0;
int l = 0;
for (int i = 1; i < n; i++) {
if (i >= l + z[l]) { // 2.
for (z[i] = 0; i + z[i] < n && s[z[i]] == s[i + z[i]]; z[i]++);
l = i;
continue;
}
int i_ = i - l;
if (i_ + z[i_] < z[l]) z[i] = z[i_]; // 1-1.
else if (i_ + z[i_] > z[l]) z[i] = z[l] - i_; // 1-3.
else { // 1-2.
for (z[i] = i_; i + z[i] < n && s[z[i]] == s[i + z[i]]; z[i]++);
if (z[i] > z[l]) l = i;
}
}
return z;
}
int stringMatching(string s, string t) {
int n = s.size(), m = t.size(), C = 0;
vector<int> z = Z(t + "$" + s);
for (int i = m + 1; i < n + m + 1; i++) {
if (z[i] == t.size()) C++;
}
return C;
}
題單
- 把之前字串匹配的題目再寫一遍!
- CSES String Functions
Manacher
終於要用到PD了!
配回文的小技巧
- 回文長度有奇有偶,找不到中心怎麼辦
- 乾脆在字元和字元間夾一個∗好了
abbab
∗a∗b∗b∗a∗b∗
ms[i]
- x<min(i,n−i)argmax(isPD(si−x…i+x+1))
- 滿足isPD(si−x…i+x+1)中最大的x
- 說穿了就是以s[i]為中心的最長回文半徑(半徑不包含自己)
s=∗a∗b∗b∗a∗b∗
zs={0,1,0,1,4,1,0,3,0,1,0}
數回文
∑ms[i]
就好了
所以要怎麼生m
zzz
$$A$$$$$$$$$$$$$B$$$…
我們現在要找ms[i]
令l=j<iargmax(j+ms[j])
展開雙翼(?)後最右邊的人
根據i的位置,可以分成2種情況:
l
l−ms[l]
l+ms[l]
所以要怎麼生m
$$A$$$$$$$$$$$$$B$$$…
1. i≤l+ms[l]
l
l−ms[l]
l+ms[l]
i
這個時候我們令i′=l−(i−l)
即以s[l]對稱過去的i
所以要怎麼生m
$$A$$$$$$$$$$$$$B$$$…
1. i≤l+ms[l]
l
l−ms[l]
l+ms[l]
i
這個時候我們令i′=l−(i−l)
即以s[l]對稱過去的i
i′
所以要怎麼生m
$$AC$$$$$D$$$$$$B$$$…
1-1. i′−ms[i′]>l−ms[l]
l
l−ms[l]
l+ms[l]
i
i′
所以要怎麼生m
$$AC$$$$$D$$$$$CB$$$…
1-1. i′−ms[i′]>l−ms[l]
l
l−ms[l]
l+ms[l]
i
i′
所以要怎麼生m
$$AC$$$$$D$$$$$CB$$$…
1-1. i′−ms[i′]>l−ms[l]
l
l−ms[l]
l+ms[l]
i
i′
⇒ms[i]=ms[i′]
所以要怎麼生m
$$A$$$$$$$C$$$$$B$$$…
1-2. i′−ms[i′]=l−ms[l]
l
l−ms[l]
l+ms[l]
i
i′
所以要怎麼生m
$$A$$$$$C$C$$$$$B$$$…
1-2. i′−ms[i′]=l−ms[l]
l
l−ms[l]
l+ms[l]
i
i′
所以要怎麼生m
$$A$$$$$C$C$$$$$B$$$…
1-2. i′−ms[i′]=l−ms[l]
l
l−ms[l]
l+ms[l]
i
i′
A=B
C=B
A=C?
所以要怎麼生m
$$A$$$$$C$C$$$$$B$$$…
1-2. i′−ms[i′]=l−ms[l]
l
l−ms[l]
l+ms[l]
i
i′
所以這個時候
只能確定ms[i]≥ms[i′]
那就繼續配下去
所以要怎麼生m
$CA$$$$$$$$D$$$$B$$$…
1-3. i′−ms[i′]<l−ms[l]
l
l−ms[l]
l+ms[l]
i
i′
所以要怎麼生m
$CA$$$$D$$$D$$$$B$$…
1-3. i′−ms[i′]<l−ms[l]
l
l−ms[l]
l+ms[l]
i
i′
所以要怎麼生m
$CA$$$$D$$$D$$$$B$$…
1-3. i′−ms[i′]<l−ms[l]
l
l−ms[l]
l+ms[l]
i
i′
A=B
所以要怎麼生m
$CA$$$$D$$$D$$$$B$$…
1-3. i′−ms[i′]<l−ms[l]
l
l−ms[l]
l+ms[l]
i
i′
⇒ms[i]=i′−l+ms[l]
所以要怎麼生m
$$A$$$$$$$$$$$$$B$$$…
2. i>l+ms[l]
l
l−ms[l]
l+ms[l]
i
所以要怎麼生m
$$A$$$$$$$$$$$$$B$$$…
2. i>l+ms[l]
l
l−ms[l]
l+ms[l]
i
沒料
只好從頭配了
Code
vector<int> M(string s) {
string br = "*";
for (auto &i : s) br += string(1, s[i]) + "*";
s = br;
int n = s.size();
vector<int> m(n);
m[0] = 0;
int l = 0;
for (int i = 1; i < n; i++) {
if (i > l + m[l]) {
for (m[i] = 0; i - m[i] - 1 >= 0 && i + m[i] + 1 < n && s[i - m[i] - 1] == s[i + m[i] + 1]; m[i]++);
l = i;
continue;
}
int i_ = l - i + l;
if (i_ - m[i_] > l - m[l]) m[i] = m[i_];
else if (i_ - m[i_] < l - m[l]) m[i] = i_ - l + m[l];
else {
for (m[i] = m[i_]; i - m[i] - 1 >= 0 && i + m[i] + 1 < n && s[i - m[i] - 1] == s[i + m[i] + 1]; m[i]++);
if (i + m[i] > l + m[l]) l = i;
}
}
return m;
}
題單
- 找不到題目…
- CSES Longest Palindrome
サ
還記得後綴嗎
s=abbab
Ss[0]=\0
Ss[1]=b
Ss[2]=ab
Ss[3]=bab
Ss[4]=bbab
Ss[5]=abbab
Ss[1]=b
Ss[2]=ab
Ss[3]=bab
Ss[4]=bbab
Ss[5]=abbab
Ss[0]=\0
還記得後綴嗎
s=abbab
Ss[0]=\0
Ss[1]=b
Ss[2]=ab
Ss[3]=bab
Ss[4]=bbab
Ss[5]=abbab
Ss[1]=b
Ss[2]=ab
Ss[3]=bab
Ss[4]=bbab
Ss[5]=abbab
Ss[0]=\0
sort
還記得後綴嗎
s=abbab
Ss[0]=\0
Ss[1]=b
Ss[2]=ab
Ss[3]=bab
Ss[4]=bbab
Ss[5]=abbab
sort
サ={0,2,5,1,3,4}
總之這就是サ
vector<int> SA(string s) {
int n = s.size();
vector<int> sa(n + 1);
vector<pair<string, int>> v(n + 1);
for (int i = 0; i <= n; i++) v[i] = {s.substr(i, n - i), i};
sort(v.begin(), v.end());
for (int i = 0; i <= n; i++) sa[i] = v[i].second;
return sa;
}
O(n2logn)
另一個サ
s.cycle(0)=abbab\0
s.cycle(3)=ab\0abb
s.cycle(4)=b\0abba
s.cycle(5)=\0abbab
s.cycle(1)=bbab\0a
s.cycle(2)=bab\0ab
s.cycle(0)=abbab\0
s.cycle(3)=ab\0abb
s.cycle(4)=b\0abba
s.cycle(5)=\0abbab
s.cycle(1)=bbab\0a
s.cycle(2)=bab\0ab
另一個サ
s.cycle(0)=abbab\0
s.cycle(3)=ab\0abb
s.cycle(4)=b\0abba
s.cycle(5)=\0abbab
s.cycle(1)=bbab\0a
s.cycle(2)=bab\0ab
s.cycle(0)=abbab\0
s.cycle(3)=ab\0abb
s.cycle(4)=b\0abba
s.cycle(5)=\0abbab
s.cycle(1)=bbab\0a
s.cycle(2)=bab\0ab
sort
另一個サ
s.cycle(0)=abbab\0
s.cycle(3)=ab\0abb
s.cycle(4)=b\0abba
s.cycle(5)=\0abbab
s.cycle(1)=bbab\0a
s.cycle(2)=bab\0ab
sort
サ={5,3,0,4,2,1}
我們其實比較喜歡這個排序
複雜度優化
先來看個小動畫
Cyc | string | rank |
---|---|---|
0 | abbab\0 | |
1 | bbab\0a | |
2 | bab\0ab | |
3 | ab\0abb | |
4 | b\0abba | |
5 | \0abbab |
Cyc | string | rank |
---|---|---|
0 | abbab\0 | |
1 | bbab\0a | |
2 | bab\0ab | |
3 | ab\0abb | |
4 | b\0abba | |
5 | \0abbab |
0a
Text
1b
Text
2b
Text
3a
Text
4b
Text
5\0
Text
Cyc | string | rank |
---|---|---|
0 | abbab\0 | |
1 | bbab\0a | |
2 | bab\0ab | |
3 | ab\0abb | |
4 | b\0abba | |
5 | \0abbab |
0a
Text
1b
Text
2b
Text
3a
Text
4b
Text
5\0
Text
Cyc | string | rank |
---|---|---|
0 | abbab\0 | |
1 | bbab\0a | |
2 | bab\0ab | |
3 | ab\0abb | |
4 | b\0abba | |
5 | \0abbab |
0a
1
1b
3
2b
3
3a
1
4b
3
5\0
0
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 1 |
1 | bbab\0a | 3 |
2 | bab\0ab | 3 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0a
1
1b
3
2b
3
3a
1
4b
3
5\0
0
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 1 |
1 | bbab\0a | 3 |
2 | bab\0ab | 3 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0a b
1
1b b
3
2b a
3
3a b
1
4b \0
3
5\0 a
0
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 1 |
1 | bbab\0a | 3 |
2 | bab\0ab | 3 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0a b
1, 3
1b b
3, 3
2b a
3, 1
3a b
1, 3
4b \0
3, 0
5\0 a
0, 1
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 1 |
1 | bbab\0a | 3 |
2 | bab\0ab | 3 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0a b
1, 3
1b b
3, 3
2b a
3, 1
3a b
1, 3
4b \0
3, 0
5\0 a
0, 1
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 1 |
1 | bbab\0a | 3 |
2 | bab\0ab | 3 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0ab
1
1bb
5
2ba
4
3ab
1
4b\0
3
5\0a
0
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 1 |
1 | bbab\0a | 5 |
2 | bab\0ab | 4 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0ab
1
1bb
5
2ba
4
3ab
1
4b\0
3
5\0a
0
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 1 |
1 | bbab\0a | 5 |
2 | bab\0ab | 4 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0ab ba
1
1bb ab
5
2ba b\0
4
3ab \0a
1
4b\0 ab
3
5\0a bb
0
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 1 |
1 | bbab\0a | 5 |
2 | bab\0ab | 4 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0ab ba
1, 4
1bb ab
5, 1
2ba b\0
4, 3
3ab \0a
1, 0
4b\0 ab
3, 1
5\0a bb
0, 5
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 1 |
1 | bbab\0a | 5 |
2 | bab\0ab | 4 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0ab ba
1, 4
1bb ab
5, 1
2ba b\0
4, 3
3ab \0a
1, 0
4b\0 ab
3, 1
5\0a bb
0, 5
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 1 |
1 | bbab\0a | 5 |
2 | bab\0ab | 4 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0abba
2
1bbab
5
2bab\0
4
3ab\0a
1
4b\0ab
3
5\0abb
0
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 2 |
1 | bbab\0a | 5 |
2 | bab\0ab | 4 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0abba
2
1bbab
5
2bab\0
4
3ab\0a
1
4b\0ab
3
5\0abb
0
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 2 |
1 | bbab\0a | 5 |
2 | bab\0ab | 4 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0abba b\0ab
2
1bbab \0abb
5
2bab\0 abba
4
3ab\0a bbab
1
4b\0ab bab\0
3
5\0abb ab\0a
0
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 2 |
1 | bbab\0a | 5 |
2 | bab\0ab | 4 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0abbab\0ab
2
1bbab\0abb
5
2bab\0abba
4
3ab\0abbab
1
4b\0abbab\0
3
5\0abbab\0a
0
Cyc | string | rank |
---|---|---|
0 | abbab\0 | 2 |
1 | bbab\0a | 5 |
2 | bab\0ab | 4 |
3 | ab\0abb | 1 |
4 | b\0abba | 3 |
5 | \0abbab | 0 |
0abbab\0ab
2
1bbab\0abb
5
2bab\0abba
4
3ab\0abbab
1
4b\0abbab\0
3
5\0abbab\0a
0
統整一下
將每個字串往後抄
目前長度個字元
把兩段字串分別用rank值表示
弄出一個pair
利用pair排序
把新的rank抄回去
統整一下
將每個字串往後抄
目前長度個字元
把兩段字串分別用rank值表示
弄出一個pair
利用pair排序
把新的rank抄回去
總共要繞logn圈
複雜度
將每個字串往後抄
目前長度個字元
把兩段字串分別用rank值表示
弄出一個pair
利用pair排序
把新的rank抄回去
總共要繞logn圈
O(n)
O(nlogn)
O(n)
複雜度
將每個字串往後抄
目前長度個字元
把兩段字串分別用rank值表示
弄出一個pair
利用pair排序
把新的rank抄回去
總共要繞logn圈
O(n)
O(nlogn)
O(n)
總複雜度O(nlog2n)
struct P {
pii p;
int id;
};
vector<int> SA(string s) {
int n = s.size();
vector<int> r(n + 1);
vector<P> v(n + 1);
vector<vector<P>> bar(n + 1);
function<bool(P, P)> cmp = [](P a, P b) {
return a.p < b.p;
};
function<void(void)> GetRank = [&]() {
for (int i = 0, j = 0; i <= n; i = j) {
while (j <= n && v[i].p == v[j].p) j++;
for (int k = i; k < j; k++) {
v[k].p.first = i;
r[v[k].id] = i;
}
}
};
for (int i = 0; i < n; i++) v[i] = {{s[i] - 'a' + 1, 0}, i};
v[n] = {{0, 0}, n};
sort(v.begin(), v.end(), [](P a, P b){return a.p < b.p;});
GetRank();
int len = 1;
while (len <= n) {
for (auto &i : v) i.p.second = r[(i.id + len) % (n + 1)];
sort(v.begin(), v.end(), cmp);
GetRank();
len <<= 1;
}
return r;
}
Code
Radix Sort
再快一點點
排序下列數字:
{41,78,38,57,59,50,72,43,46,61}
排序下列數字:
{41,78,38,57,59,50,72,43,46,61}
0 | |||
1 | |||
2 | |||
3 | |||
4 | |||
5 | |||
6 | |||
7 | |||
8 | |||
9 |
排序下列數字:
{41,78,38,57,59,50,72,43,46,61}
0 | 50 | ||
1 | 41 | 61 | |
2 | 72 | ||
3 | 43 | ||
4 | |||
5 | |||
6 | 46 | ||
7 | 57 | ||
8 | 78 | 38 | |
9 | 59 |
{41,78,38,57,59,50,72,43,46,61}
⇒{50,41,61,72,43,46,57,78,38,59}
0 | 50 | ||
1 | 41 | 61 | |
2 | 72 | ||
3 | 43 | ||
4 | |||
5 | |||
6 | 46 | ||
7 | 57 | ||
8 | 78 | 38 | |
9 | 59 |
{41,78,38,57,59,50,72,43,46,61}
⇒{50,41,61,72,43,46,57,78,38,59}
0 | |||
1 | |||
2 | |||
3 | |||
4 | |||
5 | |||
6 | |||
7 | |||
8 | |||
9 |
{41,78,38,57,59,50,72,43,46,61}
⇒{50,41,61,72,43,46,57,78,38,59}
0 | |||
1 | |||
2 | |||
3 | 38 | ||
4 | 41 | 43 | 46 |
5 | 50 | 57 | 59 |
6 | 61 | ||
7 | 72 | 78 | |
8 | |||
9 |
{41,78,38,57,59,50,72,43,46,61}
⇒{50,41,61,72,43,46,57,78,38,59}
⇒{38,41,43,46,50,57,59,61,72,78}
0 | |||
1 | |||
2 | |||
3 | 38 | ||
4 | 41 | 43 | 46 |
5 | 50 | 57 | 59 |
6 | 61 | ||
7 | 72 | 78 | |
8 | |||
9 |
複雜度
將每個字串往後抄
目前長度個字元
把兩段字串分別用rank值表示
弄出一個pair
利用pair排序
把新的rank抄回去
總共要繞logn圈
O(n)
O(nlogn)
O(n)
複雜度
將每個字串往後抄
目前長度個字元
把兩段字串分別用rank值表示
弄出一個pair
利用pair排序
把新的rank抄回去
總共要繞logn圈
O(n)
O(n)
O(n)
複雜度
將每個字串往後抄
目前長度個字元
把兩段字串分別用rank值表示
弄出一個pair
利用pair排序
把新的rank抄回去
總共要繞logn圈
O(n)
O(n)
O(n)
總複雜度O(nlogn)
struct P {
pii p;
int id;
};
vector<int> SA(string s) {
int n = s.size();
vector<int> r(n + 1);
vector<P> v(n + 1);
vector<vector<P>> bar(n + 1);
function<void(void)> RS = [&]() {
for (auto &i : v) bar[i.p.second].push_back(i);
for (int i = 0, j = 0; i <= n; i++) {
for (auto &k : bar[i]) v[j++] = k;
bar[i].clear();
}
for (auto &i : v) bar[i.p.first].push_back(i);
for (int i = 0, j = 0; i <= n; i++) {
for (auto &k : bar[i]) v[j++] = k;
bar[i].clear();
}
};
function<void(void)> GetRank = [&]() {
for (int i = 0, j = 0; i <= n; i = j) {
while (j <= n && v[i].p == v[j].p) j++;
for (int k = i; k < j; k++) {
v[k].p.first = i;
r[v[k].id] = i;
}
}
};
for (int i = 0; i < n; i++) v[i] = {{s[i] - 'a' + 1, 0}, i};
v[n] = {{0, 0}, n};
sort(v.begin(), v.end(), [](P a, P b){return a.p < b.p;});
GetRank();
int len = 1;
while (len <= n) {
for (auto &i : v) i.p.second = r[(i.id + len) % (n + 1)];
RS();
GetRank();
len <<= 1;
}
return r;
}
Code
題單
- CSES String 50%破台
- TIOJ 2155
LCP
LCP
- Longest Common Prefix
- 最長共同前綴
LCP(abbab,abab)=2
abbab
abab
接下來的目標
生出一個資料結構,可以幫我們查詢任意兩組後綴的LCP
接下來的目標
生出一個資料結構,可以幫我們查詢任意兩組後綴的LCP
s=abbab\0
Ss[0]=\0
Ss[1]=b\0
Ss[2]=ab\0
Ss[3]=bab\0
Ss[4]=bbab\0
Ss[5]=abbab\0
LCPs(1,4)
=LCP(b,bbab)
=1
接下來的目標
生出一個資料結構,可以幫我們查詢任意兩組後綴的LCP
好啦我們還是比較喜歡cycle
接下來的目標
生出一個資料結構,可以幫我們查詢任意兩個cycle的LCP
接下來的目標
生出一個資料結構,可以幫我們查詢任意兩個cycle的LCP
s=abbab\0
s.cycle(0)=abbab\0
s.cycle(1)=bbab\0a
s.cycle(2)=bab\0ab
s.cycle(3)=ab\0abb
s.cycle(4)=b\0abba
s.cycle(5)=\0abbab
LCPs(4,1)
=LCP(b\0abba,bbab\0a)
=1
做法
還記得剛剛的rank陣列嗎
string | a | b | b | a | b | \0 |
rank | 1 | 3 | 3 | 1 | 3 | 0 |
string | ab | bb | bb | ab | b\0 | \0a |
rank | 1 | 5 | 4 | 1 | 3 | 0 |
string | abba | bbab | bab\0 | ab\0a | b\0ab | \0abb |
rank | 2 | 5 | 4 | 1 | 3 | 0 |
string | 放 | 不 | 下 | 了 | ... | ... |
rank | 2 | 5 | 4 | 1 | 3 | 0 |
把所有rank存起來的話…
把所有rank存起來的話…
- rank[i][j]代表s.sycle(j)在第i輪的排名
- 如果我們想知道s.cycle(x)和s.cycle(y)的第2i個前綴是否相同
- 就去查rank[i][x],rank[i][y],如果數字相同就代表前綴相同
把所有rank存起來的話…
- 這代表我們可以二分搜前綴長度!
- 複雜度:O(logn)每次查詢
- (不考慮建rank的複雜度)
把所有rank存起來的話…
- 這代表我們可以二分搜前綴長度!
- 複雜度:O(logn)每次查詢
- (不考慮建rank的複雜度)
但是空間複雜度呢?
空間炸裂
讓我們換個方式
Ss[5]=\0
Ss[3]=ab\0
Ss[0]=abbab\0
Ss[4]=b\0
Ss[2]=bab\0
Ss[1]=bbab\0
s=abbab
我們開一個陣列l
使得l[i]=排名在第i的後綴和排名在第i+1的後綴做LCP
Ss[5]=\0
Ss[3]=ab\0
Ss[0]=abbab\0
Ss[4]=b\0
Ss[2]=bab\0
Ss[1]=bbab\0
s=abbab
我們開一個陣列l
使得l[i]=排名在第i的後綴和排名在第i+1的後綴做LCP
l[0]=0
l[1]=2
l[2]=0
l[3]=1
l[4]=1
Ss[5]=\0
Ss[3]=ab\0
Ss[0]=abbab\0
Ss[4]=b\0
Ss[2]=bab\0
Ss[1]=bbab\0
l[0]=0
l[1]=2
l[2]=0
l[3]=1
l[4]=1
如果我想要找LCP(Ss[3],Ss[2])
Ss[5]=\0
Ss[3]=ab\0
Ss[0]=abbab\0
Ss[4]=b\0
Ss[2]=bab\0
Ss[1]=bbab\0
l[0]=0
l[1]=2
l[2]=0
l[3]=1
l[4]=1
如果我想要找LCP(Ss[3],Ss[2])
答案會是min(l[1…4])
找LCP會變成求區間最小值(RMQ)
拿棵線段樹什麼的維護l就好了
生出l
先來看個小動畫
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=?
l[2]=?
l[3]=?
l[4]=?
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=?
l[2]=?
l[3]=?
l[4]=?
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=?
l[2]=?
l[3]=?
l[4]=?
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=?
l[2]=?
l[3]=?
l[4]=?
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=?
l[2]=?
l[3]=?
l[4]=?
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]=?
l[4]=?
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]=?
l[4]=?
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]=?
l[4]=?
LCP(Ss[0],Ss[3])=2
⇒LCP(Ss[1],Ss[4])=1
⇒LCP(Ss[1],Ss[2])≥1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]=?
l[4]≥1
LCP(Ss[0],Ss[3])=2
⇒LCP(Ss[1],Ss[4])=1
⇒LCP(Ss[1],Ss[2])≥1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]=?
l[4]≥1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]=?
l[4]=1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]=?
l[4]=1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]=?
l[4]=1
LCP(Ss[1],Ss[2])=1
⇒LCP(Ss[2],Ss[3])=0
⇒LCP(Ss[2],Ss[4])≥0
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]≥0
l[4]=1
LCP(Ss[1],Ss[2])=1
⇒LCP(Ss[2],Ss[3])=0
⇒LCP(Ss[2],Ss[4])≥0
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]≥0
l[4]=1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]≥0
l[4]=1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]=1
l[4]=1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=?
l[1]=2
l[2]=?
l[3]=1
l[4]=1
LCP(Ss[2],Ss[4])=1
⇒LCP(Ss[3],Ss[5])=0
⇒LCP(Ss[3],Ss[5])≥0
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]≥0
l[1]=2
l[2]=?
l[3]=1
l[4]=1
LCP(Ss[2],Ss[4])=1
⇒LCP(Ss[3],Ss[5])=0
⇒LCP(Ss[3],Ss[5])≥0
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]≥0
l[1]=2
l[2]=?
l[3]=1
l[4]=1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=0
l[1]=2
l[2]=?
l[3]=1
l[4]=1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=0
l[1]=2
l[2]=?
l[3]=1
l[4]=1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=0
l[1]=2
l[2]=?
l[3]=1
l[4]=1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=0
l[1]=2
l[2]=0
l[3]=1
l[4]=1
s=abbab
50
3ab0
0abbab0
4b0
2bab0
1bbab0
l[0]=0
l[1]=2
l[2]=0
l[3]=1
l[4]=1
動動腦
- 每一次一定都可以用前一次配對的東西嗎?
- 會不會剛好配完新的區間在新的目標的往下?
- 時間複雜度?
其實是懶得寫證明
vector<int> LCP(string s, vector<int> &r) {
int n = s.size();
vector<int> p(n + 1), l(n);
for (int i = 0; i <= n; i++) p[r[i]] = i;
int len = 0;
for (int i = 0; i < n; i++) {
int j = p[r[i] - 1];
while (i + len < n && j + len < n && s[i + len] == s[j + len]) len++;
l[r[i] - 1] = len;
if (len) len--;
}
return l;
}
Code
題單
- CSES String 80%破台
沒了
- Suffix Automaton (另一個サ)
- Main-Lorentz Algorithm
- Lyndon Factorization (寫不出這題的可以參考)
- String Matching with FFT
各種毒瘤延伸:
推一個網站
Strings
By pring
Strings
- 576