Strings
227 高翊恩
關於講師
- OJ id: pring、pringDeSu
- 喜歡壓一行
- 左閉右開
會提到的東西
- 字串匹配
- 數回文
- サ及LCP
關於string
C++ STL string
- unsigned int size()
- unsigned int find(char)
- string substr(from, length) (也會寫成\(s_{l\dots 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\))
- 我喜歡叫它\(P_s[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|\leq5000\)
- \(t.size()\leq10^5,\sum |t_i|<10^6\)
DP解
- 令\(dp[i]=P_s[i]的組合方法\)
- \(dp_{i+1}=\sum\limits_{j\in t}[(s_{i+1-|j|\dots i+1}==j)\times dp_{i+1-j}]\)
- 轉移太久了...
高級一點的DP解
- 令\(dp[i]=P_s[i]的組合方法\)
- \(dp_{i+1}=\sum\limits_{j\in t}[(s_{i+1-j\dots i+1}==j)\times dp_{i+1-j}]\)
- 上面這個可以優化成對所有結尾在\(i+1\)的子字串,看有沒有在\(t\)裡面
- 高級技巧:反著做
- 把\(t\)弄成一個set
高級一點的DP解
- 令\(dp[i]=P_s[i]的組合方法\)
- \(dp_{i+1}=\sum\limits_{j\in t}[(s_{i+1-j\dots i+1}==j)\times dp_{i+1-j}]\)
- 上面這個可以優化成對所有結尾在\(i+1\)的子字串,看有沒有在\(t\)裡面
- 高級技巧:反著做
- 把\(t\)弄成一個set
\(\Omicron(n^2\log n)\)
來把\(\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進位制
\(\Sigma=\{0\dots9,A\dots F\}\)
轉換方式:
\(\mathbf{8E5C}_{16}={36444}_{10}\)
26進位制
\(\Sigma=\{a\dots z\}\)
轉換方式:
\(\mathbf{pring}_{26}={7159184}_{10}\)
問題1
\(a\rightarrow0\)
\(\mathbf{pring}_{26}=\mathbf{aaaaapring}_{26}\)
27進位制
\(\Sigma=\{a\dots z\}\)
\(\{a,b,\dots,z\}\rightarrow\{1,2,\dots,26\}\)
轉換方式:
\(\mathbf{pring}_{27}={8864296}_{10}\)
問題2
數字太大啦!!
所以送它一個\(\%\)
27進位制
\(\Sigma=\{a\dots z\}\)
\(\{a,b,\dots,z\}\rightarrow\{1,2,\dots,26\}\)
最後要\(\%(1e9+7)\)
轉換方式:
\(\mathbf{pring}_{27}={8864296}_{10}\)
Hash有兩種
正著做:
\(Hash(s)=(s_0p^0+s_1p^1+s_2p^2+\dots+s_{|s|-1}p^{|s|-1})\%M\)
反著做:
\(hsaH(s)=(s_0p^{|s|-1}+s_1p^{|s|-2}+s_2p^{|s|-3}+\dots+s_{|s|-1}p^0)\%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))\),只用\(\Omicron(1)\)求出\(Hash(s.substr(1, i))\)?
遞推法
\(s_0s_1s_2s_3s_4\longrightarrow h\)
\(s_1s_2s_3s_4\longrightarrow h-s_0p^{|t|}\)
\(s_1s_2s_3s_4\_\longrightarrow (h-s_0p^{|t|})\times 27\)
\(s_1s_2s_3s_4s_5\longrightarrow (h-s_0p^{|t|})\times 27+s_5\)
記得有模運算
這裡用的是\(hsaH\)
前綴和
開一個陣列\(Pref\)記錄\(Hash(P_s[i])\)
則可以用\(\Omicron(1)\)求出\((\sum\limits_{i=l}^{r-1}s_ip^i)\%M\)
可是我們要的是\((\sum\limits_{i=l}^{r-1}s_ip^{i-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\div Q=P\times Q^{-1}\)
\(\ \ \ \ \ Q\times Q^{-1}=1\)
\(\Rightarrow Q\times Q^{-1}\equiv1(\mod M)\)
所以想辦法找到\(Q^{-1}\)就好了
怎麼找?
費馬小定理
\(a^p\equiv a(\mod p)\)
\(a<p\Rightarrow a^{p-1}\equiv1(\mod p)\)
\(\Rightarrow a^{p-2}\equiv a^{-1}(\mod p)\)
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\in s.CPS\)
則\(t.CPS\subset s.CPS\)
次序問題
-
根據定義:\(s.CPS(1)\)為「除了\(s\)本身的最長\(CPS\)」
- 令\(t=s.CPS(1)\),則\(t.CPS(1)\)就是「除了\(t\)本身的最長\(CPS\)」
- 根據剛剛推過的,\(t.CPS(1)\in 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\)為\(P_s\)的其中一個元素
則有\(t.CPS\subseteq P_s\)
如果我們對所有\(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個前綴的所有共同前後綴時
\(P_s[7].CPS(1)\)
\(P_s[7].CPS(2)\)
\(P_s[7].CPS(3)\)
終於…
- 壓成一個一維陣列
- 利用這個陣列我們可以求出所有前綴的任何\(CPS\)
- 定義\(\pi_s[i]:P_s[i]\)的「次長共同前後綴」(\(CPS(1)\))
\(s=abbabbab\)
\(\pi_s=\{-1,0,0,0,1,2,3,4,5\}\)
暫停一下
找出\(\pi\)陣列
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(n^3)\)
超級慢
DP一下
初始條件:
\(\pi[0]=-1,\pi[1]=0\)
觀察性質
\(P_s[\pi_s[i+1]-1]\in P_s[i].CPS\)
\(\underbrace{\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$}\dots\)
\(P_s[i+1]\)
假設我們要找\(\pi_s[i+1]\)
所以就有找法了
找\(\pi_s[i+1]\)時
令\(j=1,2,\dots\)
如果\(P_s[i].CPS(j)\)的「下一個字元」\(==s[i+1]\)
則\(\pi_s[i+1]=P_s[i].CPS(j)+1\)
\(\underbrace{\$\$\$\$\$\$\$?\$\$\$\$\$\$\$\$\$\$?}\dots\)
\(P_s[i+1]\)
\(s=abbacabbab\dots\)
\(\pi_s=\{-1, 0, 0, 0, 1, 0, 1, 2, 3, 4, ?\}\)
\(abbacabba\ b\)
\(s=abbacabbab\dots\)
\(\pi_s=\{-1, 0, 0, 0, 1, 0, 1, 2, 3, 4, ?\}\)
\(abbacabba\ b\)
\(P_s[9].CPS(1)=\pi_s[9]=4\)
\(s=abbacabbab\dots\)
\(\pi_s=\{-1, 0, 0, 0, 1, 0, 1, 2, 3, 4, ?\}\)
\(abbacabba\ b\)
\(P_s[9].CPS(1)=\pi_s[9]=4\)
\(s=abbacabbab\dots\)
\(\pi_s=\{-1, 0, 0, 0, 1, 0, 1, 2, 3, 4, ?\}\)
\(abbacabba\ b\)
\(P_s[9].CPS(2)=\pi_s[\pi_s[9]]=1\)
\(s=abbacabbab\dots\)
\(\pi_s=\{-1, 0, 0, 0, 1, 0, 1, 2, 3, 4, ?\}\)
\(abbacabba\ b\)
\(P_s[9].CPS(2)=\pi_s[\pi_s[9]]=1\)
\(s=abbacabbab\dots\)
\(\pi_s=\{-1, 0, 0, 0, 1, 0, 1, 2, 3, 4, ?\}\)
\(abbacabba\ b\)
\(\pi_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]\neq t[j]\)時候:
- 選某些\(x\in S\)的情況下可以確保\(t\)可以完美地配到\(t[j-x]\)
- 其他狀況的\(x\)都一定會在配到\(t[j-x]\)前就爛掉
那我們這個操作就可以將\(t\)向右推\(S\)中最小的
\(\dots\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\dots\)
\(t\)往右推了\(x\)格
\(\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\)
\(t[j]\)
\(\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\)
\(t[j-x]\)
證明當\(s[i]\neq t[j]\)時候:
- 選某些\(x\in S\)的情況下可以確保\(t\)可以完美地配到\(t[j-x]\)
- 其他狀況的\(x\)都一定會在配到\(t[j-x]\)前就爛掉
那我們這個操作就可以將\(t\)向右推\(S\)中最小的
\(\dots\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\dots\)
\(t\)往右推了\(x\)格
好啦其實就是\(CPS\)啦
\(\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\)
\(t[j]\)
\(\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\)
\(t[j-x]\)
證明當\(s[i]\neq t[j]\)時候:
- 選某些\(x\in S\)的情況下可以確保\(t\)可以完美地配到\(t[j-x]\)
- 其他狀況的\(x\)都一定會在配到\(t[j-x]\)前就爛掉
那我們這個操作就可以將\(t\)向右推\(S\)中最小的
\(\dots\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\dots\)
\(t\)往右推了\(x\)格
好啦其實就是\(CPS\)啦
\(\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\)
\(t[j]\)
\(\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\)
\(t[j-x]\)
假設\(j-x\notin P_t[j].CPS\)
且成功配到藍色格子
\(\dots\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\dots\)
\(\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\)
\(t[j]\)
\(\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\)
假設\(j-x\notin P_t[j].CPS\)
且成功配到藍色格子
\(\dots\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\dots\)
\(t[j-x]\)
\(\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\)
\(t[j]\)
\(\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\)
假設\(j-x\notin P_t[j].CPS\)
且成功配到藍色格子
\(\dots\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\$\ \$\dots\)
則\(j-x\in P_t[j].CPS\)
\(\Rightarrow\)只有推\(x\)格的人可以活下來
\(t[j-x]\)
所以發現錯誤的時候
就找現在配到的字串的\(CPS(1)\ (\pi)\)
然後從剛剛錯的地方繼續
\(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\)連起來
然後做\(\pi\)
然後數\(\$\)後面哪些的\(\pi\)值是\(|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;
}
為什麼不先講這個\(\dots\)
Prefix Automaton
前綴自動機
\(\dots abbaa\dots\)
\(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;
}
建表:\(\Omicron(n)\)
配對:\(\Omicron(1)/\)每個字元
阿字串匹配不是直接用\(\pi\)做的
幹嘛還要學PA
Compressed String
給定三字串\(s_0,s_1,t\)和一整數\(n\)
令\(s_{i+1}=5\times s_i+6\times s_{i-1},i\in\mathbb{Z}^+\)
求在\(s_n\)裡面可以找到幾個\(t\)
Compressed String
給定三字串\(s_0,s_1,t\)和一整數\(n\)
令\(s_{i+1}=5\times s_i+6\times s_{i-1},i\in\mathbb{Z}^+\)
求在\(s_n\)裡面可以找到幾個\(t\)
顯然地
\(|s_n|=\frac{1}{7}\{[6\times(-1)^n+6^n]|s_0|+[6^n-(-1)^n]|s_1|\}\)
直接用\(\pi\)會炸
直接用\(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
\(一台自動機=\langle Q,\Sigma,\delta,q_0,F\rangle\)
\(=\langle狀態,字母表,轉移函式,初始狀態,終止狀態\rangle\)
\(Q=圈圈們\)
\(\Sigma=\{a,b\}\)
\(\delta=箭頭們\)
\(q_0=橘色圈圈\)
\(F=綠色圈圈們\)
走走看:\(abbaabbab\)
真 ● PA
\(t=abbab\)
走走看:\(abbaabbab\)
還記得Trie嗎
在Trie上做PA
\(\delta(q,c)=\)
\(在q所代表的字串+c中,\)
有出現在樹上的最長後綴
在Trie上做PA
\(\delta(q,c)=\)
\(在q所代表的字串+c中,\)
有出現在樹上的最長後綴
在Trie上做PA
\(\delta(q,c)=\)
\(在q所代表的字串+c中,\)
有出現在樹上的最長後綴
在Trie上做PA
\(\delta(q,c)=\)
\(在q所代表的字串+c中,\)
有出現在樹上的最長後綴
走走看:\(abbaabbab\)
但是這樣太久了
要換個做法
\(q\)的樹上後綴
- 有在樹上的後綴
- 由長排到短
- Suffix in Trie
- 我喜歡叫它SIT
SIT性質
- \(q_i.SIT(0)=q_i\)
- \(q_i.SIT(2)=q_i.SIT(1).SIT(1)\)
- 所以我們對所有節點做\(SIT(1)\)就可以找到所有節點的\(SIT(n)\)
SIT性質
- \(q_i.SIT(0)=q_i\)
- \(q_i.SIT(2)=q_i.SIT(1).SIT(1)\)
- 所以我們對所有節點做\(SIT(1)\)就可以找到所有節點的\(SIT(n)\)
- 我們叫它Suffix Link
建立Suffix Link
初始條件:
\(q[0].SIT(1)=nullptr\)
\(q[i].SIT(1)=q[0]\)
\(\forall q[i]\in depth(1)\)
\(\$\$\$\$\$\$\$\$\$\ \$\)
藍色\(\in Trie\)
假設紅色為\(q_i.SIT(1)\)
則紅色\(\in Trie\)
則綠色\(\in Trie\)
則綠色\(\in\)藍色的\(SIT\)
\(q_i\)
\(q_i\)
所以就有找法了
找\(q[i].SIT(1)\)時
令\(j=1,2,\dots\)
如果\(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\)
阿這樣子要怎麼轉移
\(\delta(q_i,c)\)
- \(now=q_i\)
- 如果\(now\)可以走\(c\),則\(\delta(q_i,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!
- 踩上一個點時,該點代表\(s_{now}.SIT(1)\)
- 但如果\(s_{now}.SIT(k)\in F\),那它應該也要被配到
- 走一遍\(SL\)路徑
- 相當於是所有的Answer in Trie
99% completed!
- 踩上一個點時,該點代表\(s_{now}.SIT(1)\)
- 但如果\(s_{now}.SIT(k)\in F\),那它應該也要被配到
- 走一遍\(SL\)路徑
- 相當於是要找到所有的Answer in Trie
- 我喜歡叫它AIT
99% completed!
- 跑一遍\(SL\)找\(AIT\)有點浪費時間\(\dots\)
- 壓縮\(SL\)的路徑,對每一個點\(q\)都存一條指向\(q'|q'\in q.SIT\cup 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
\(z_s[i]\)
- \(\argmax\limits_{r<|s|-i}(P_s[r]==s_{i...i+r})\)
- 滿足\(P_s[r]==s_{i\dots i+r}\)中最大的\(r\)
- 說穿了就是從\(s[i]\)和\(s[0]\)開始最多能夠往右配幾個
\(s=abbabbab\)
\(z_s=\{\_,0,0,5,0,0,2,0\}\)
字串匹配
對\(t+"\$"+s\)做\(z\)
就好了
所以要怎麼生\(z\)
zzz
\(\$\$\$\$\$\$A\$\$\$\$\$\$\$\$\$B\$\$\$\dots\)
令\(l=\argmax\limits_j(j+z_s[j])\)
現在已經配到最右邊的那個人
我們現在要找\(z_s[i]\)
根據\(i\)的位置,可以分成\(2\)種情況:
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
所以要怎麼生\(z\)
\(\$\$\$\$\$\$A\$\$\$\$\$\$\$\$\$B\$\$\$\dots\)
- \(i<l+z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
這個時候我們令\(i'=i-l\)
即Prefix中對應到的\(i\)
\(i\)
所以要怎麼生\(z\)
\(\$\$\$\$\$\$A\$\$\$\$\$\$\$\$\$B\$\$\$\dots\)
- \(i<l+z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
這個時候我們令\(i'=i-l\)
即Prefix中對應到的\(i\)
\(i\)
\(i'\)
所以要怎麼生\(z\)
\(\$\$C\$\$DA\$\$\$\$\$\$\$\$\$B\$\$\$\dots\)
1-1. \(i'+z_s[i']<z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
\(i'\)
所以要怎麼生\(z\)
\(\$\$C\$\$DA\$\$\$\$\$C\$\$DB\$\$\$\dots\)
1-1. \(i'+z_s[i']<z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
\(i'\)
所以要怎麼生\(z\)
\(\$\$C\$\$DA\$\$\$\$\$C\$\$DB\$\$\$\dots\)
1-1. \(i'+z_s[i']<z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
\(i'\)
\(\Rightarrow z_s[i]=z_s[i']\)
所以要怎麼生\(z\)
\(\$\$\$C\$\$A\$\$\$\$\$\$\$\$\$B\$\$\$\dots\)
1-2. \(i'+z_s[i']=z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
\(i'\)
所以要怎麼生\(z\)
\(\$\$\$C\$\$A\$\$\$\$\$\$C\$\$B\$\$\$\dots\)
1-2. \(i'+z_s[i']=z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
\(i'\)
所以要怎麼生\(z\)
\(\$\$\$C\$\$A\$\$\$\$\$\$C\$\$B\$\$\$\dots\)
1-2. \(i'+z_s[i']=z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
\(i'\)
\(A\neq B\)
\(C\neq B\)
\(A\neq C?\)
所以要怎麼生\(z\)
\(\$\$\$C\$\$A\$\$\$\$\$\$C\$\$B\$\$\$\dots\)
1-2. \(i'+z_s[i']=z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
\(i'\)
所以這個時候
只能確定\(z_s[i]\geq z_s[i']\)
那就繼續配下去
所以要怎麼生\(z\)
\(\$\$\$\$C\$AD\$\$\$\$\$\$\$\$B\$\$\$\dots\)
1-3. \(i'+z_s[i']>z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
\(i'\)
所以要怎麼生\(z\)
\(\$\$\$\$C\$AD\$\$\$\$\$\$C\$B\$\$\$\dots\)
1-3. \(i'+z_s[i']>z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
\(i'\)
所以要怎麼生\(z\)
\(\$\$\$\$C\$AD\$\$\$\$\$\$C\$B\$\$\$\dots\)
1-3. \(i'+z_s[i']>z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
\(i'\)
\(A\neq B\)
所以要怎麼生\(z\)
\(\$\$\$\$C\$AD\$\$\$\$\$\$C\$B\$\$\$\dots\)
1-3. \(i'+z_s[i']>z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
\(i'\)
\(\Rightarrow z_s[i]=z_s[l]-i'\)
所以要怎麼生\(z\)
\(\$\$\$\$\$\$A\$\$\$\$\$\$\$\$\$B\$\$\$\dots\)
2. \(i\geq l+z_s[l]\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
所以要怎麼生\(z\)
\(\$\$\$\$\$\$A\$\$\$\$\$\$\$\$\$B\$\$\$\dots\)
\(l\)
\(z_s[l]\)
\(l+z_s[l]\)
\(i\)
沒料
只好從頭配了
2. \(i\geq l+z_s[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*\)
\(m_s[i]\)
- \(\argmax\limits_{x<\min(i,n-i)}(\operatorname{isPD}(s_{i-x\dots i+x+1}))\)
- 滿足\(isPD(s_{i-x\dots i+x+1})\)中最大的\(x\)
- 說穿了就是以\(s[i]\)為中心的最長回文半徑(半徑不包含自己)
\(s=*a*b*b*a*b*\)
\(z_s=\{0,1,0,1,4,1,0,3,0,1,0\}\)
數回文
\(\sum m_s[i]\)
就好了
所以要怎麼生\(m\)
zzz
\(\$\$A\$\$\$\$\$\$\$\$\$\$\$\$\$B\$\$\$\dots\)
我們現在要找\(m_s[i]\)
令\(l=\argmax\limits_{j<i}(j+m_s[j])\)
展開雙翼(?)後最右邊的人
根據\(i\)的位置,可以分成\(2\)種情況:
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
所以要怎麼生\(m\)
\(\$\$A\$\$\$\$\$\$\$\$\$\$\$\$\$B\$\$\$\dots\)
1. \(i\leq l+m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
這個時候我們令\(i'=l-(i-l)\)
即以\(s[l]\)對稱過去的\(i\)
所以要怎麼生\(m\)
\(\$\$A\$\$\$\$\$\$\$\$\$\$\$\$\$B\$\$\$\dots\)
1. \(i\leq l+m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
這個時候我們令\(i'=l-(i-l)\)
即以\(s[l]\)對稱過去的\(i\)
\(i'\)
所以要怎麼生\(m\)
\(\$\$AC\$\$\$\$\$D\$\$\$\$\$\$B\$\$\$\dots\)
1-1. \(i'-m_s[i']>l-m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
\(i'\)
所以要怎麼生\(m\)
\(\$\$AC\$\$\$\$\$D\$\$\$\$\$CB\$\$\$\dots\)
1-1. \(i'-m_s[i']>l-m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
\(i'\)
所以要怎麼生\(m\)
\(\$\$AC\$\$\$\$\$D\$\$\$\$\$CB\$\$\$\dots\)
1-1. \(i'-m_s[i']>l-m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
\(i'\)
\(\Rightarrow m_s[i]=m_s[i']\)
所以要怎麼生\(m\)
\(\$\$A\$\$\$\$\$\$\$C\$\$\$\$\$B\$\$\$\dots\)
1-2. \(i'-m_s[i']=l-m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
\(i'\)
所以要怎麼生\(m\)
\(\$\$A\$\$\$\$\$C\$C\$\$\$\$\$B\$\$\$\dots\)
1-2. \(i'-m_s[i']=l-m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
\(i'\)
所以要怎麼生\(m\)
\(\$\$A\$\$\$\$\$C\$C\$\$\$\$\$B\$\$\$\dots\)
1-2. \(i'-m_s[i']=l-m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
\(i'\)
\(A\neq B\)
\(C\neq B\)
\(A\neq C?\)
所以要怎麼生\(m\)
\(\$\$A\$\$\$\$\$C\$C\$\$\$\$\$B\$\$\$\dots\)
1-2. \(i'-m_s[i']=l-m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
\(i'\)
所以這個時候
只能確定\(m_s[i]\geq m_s[i']\)
那就繼續配下去
所以要怎麼生\(m\)
\(\$CA\$\$\$\$\$\$\$\$D\$\$\$\$B\$\$\$\dots\)
1-3. \(i'-m_s[i']<l-m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
\(i'\)
所以要怎麼生\(m\)
\(\$CA\$\$\$\$D\$\$\$D\$\$\$\$B\$\$\dots\)
1-3. \(i'-m_s[i']<l-m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
\(i'\)
所以要怎麼生\(m\)
\(\$CA\$\$\$\$D\$\$\$D\$\$\$\$B\$\$\dots\)
1-3. \(i'-m_s[i']<l-m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
\(i'\)
\(A\neq B\)
所以要怎麼生\(m\)
\(\$CA\$\$\$\$D\$\$\$D\$\$\$\$B\$\$\dots\)
1-3. \(i'-m_s[i']<l-m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
\(i'\)
\(\Rightarrow m_s[i]=i'-l+m_s[l]\)
所以要怎麼生\(m\)
\(\$\$A\$\$\$\$\$\$\$\$\$\$\$\$\$B\$\$\$\dots\)
2. \(i>l+m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[l]\)
\(i\)
所以要怎麼生\(m\)
\(\$\$A\$\$\$\$\$\$\$\$\$\$\$\$\$B\$\$\$\dots\)
2. \(i>l+m_s[l]\)
\(l\)
\(l-m_s[l]\)
\(l+m_s[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;
}
題單
- 找不到題目\(\dots\)
- CSES Longest Palindrome
サ
還記得後綴嗎
\(s=abbab\)
\(S_s[0]=\backslash0\)
\(S_s[1]=b\)
\(S_s[2]=ab\)
\(S_s[3]=bab\)
\(S_s[4]=bbab\)
\(S_s[5]=abbab\)
\(S_s[1]=b\)
\(S_s[2]=ab\)
\(S_s[3]=bab\)
\(S_s[4]=bbab\)
\(S_s[5]=abbab\)
\(S_s[0]=\backslash0\)
還記得後綴嗎
\(s=abbab\)
\(S_s[0]=\backslash0\)
\(S_s[1]=b\)
\(S_s[2]=ab\)
\(S_s[3]=bab\)
\(S_s[4]=bbab\)
\(S_s[5]=abbab\)
\(S_s[1]=b\)
\(S_s[2]=ab\)
\(S_s[3]=bab\)
\(S_s[4]=bbab\)
\(S_s[5]=abbab\)
\(S_s[0]=\backslash0\)
sort
還記得後綴嗎
\(s=abbab\)
\(S_s[0]=\backslash0\)
\(S_s[1]=b\)
\(S_s[2]=ab\)
\(S_s[3]=bab\)
\(S_s[4]=bbab\)
\(S_s[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;
}
\(\Omicron(n^2\log n)\)
另一個サ
\(s.cycle(0)=abbab\backslash0\)
\(s.cycle(3)=ab\backslash0abb\)
\(s.cycle(4)=b\backslash0abba\)
\(s.cycle(5)=\backslash0abbab\)
\(s.cycle(1)=bbab\backslash0a\)
\(s.cycle(2)=bab\backslash0ab\)
\(s.cycle(0)=abbab\backslash0\)
\(s.cycle(3)=ab\backslash0abb\)
\(s.cycle(4)=b\backslash0abba\)
\(s.cycle(5)=\backslash0abbab\)
\(s.cycle(1)=bbab\backslash0a\)
\(s.cycle(2)=bab\backslash0ab\)
另一個サ
\(s.cycle(0)=abbab\backslash0\)
\(s.cycle(3)=ab\backslash0abb\)
\(s.cycle(4)=b\backslash0abba\)
\(s.cycle(5)=\backslash0abbab\)
\(s.cycle(1)=bbab\backslash0a\)
\(s.cycle(2)=bab\backslash0ab\)
\(s.cycle(0)=abbab\backslash0\)
\(s.cycle(3)=ab\backslash0abb\)
\(s.cycle(4)=b\backslash0abba\)
\(s.cycle(5)=\backslash0abbab\)
\(s.cycle(1)=bbab\backslash0a\)
\(s.cycle(2)=bab\backslash0ab\)
sort
另一個サ
\(s.cycle(0)=abbab\backslash0\)
\(s.cycle(3)=ab\backslash0abb\)
\(s.cycle(4)=b\backslash0abba\)
\(s.cycle(5)=\backslash0abbab\)
\(s.cycle(1)=bbab\backslash0a\)
\(s.cycle(2)=bab\backslash0ab\)
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\backslash0\)
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\backslash0\)
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\backslash0\)
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\backslash0\)
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\ \backslash0\)
3
\(_5\backslash0\ 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\ \backslash0\)
3, 0
\(_5\backslash0\ 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\ \backslash0\)
3, 0
\(_5\backslash0\ 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\backslash0\)
3
\(_5\backslash0a\)
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\backslash0\)
3
\(_5\backslash0a\)
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\backslash0\)
4
\(_3ab\ \backslash0a\)
1
\(_4b\backslash0\ ab\)
3
\(_5\backslash0a\ 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\backslash0\)
4, 3
\(_3ab\ \backslash0a\)
1, 0
\(_4b\backslash0\ ab\)
3, 1
\(_5\backslash0a\ 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\backslash0\)
4, 3
\(_3ab\ \backslash0a\)
1, 0
\(_4b\backslash0\ ab\)
3, 1
\(_5\backslash0a\ 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\backslash0\)
4
\(_3ab\backslash0a\)
1
\(_4b\backslash0ab\)
3
\(_5\backslash0abb\)
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\backslash0\)
4
\(_3ab\backslash0a\)
1
\(_4b\backslash0ab\)
3
\(_5\backslash0abb\)
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\backslash0ab\)
2
\(_1bbab\ \backslash0abb\)
5
\(_2bab\backslash0\ abba\)
4
\(_3ab\backslash0a\ bbab\)
1
\(_4b\backslash0ab\ bab\backslash0\)
3
\(_5\backslash0abb\ ab\backslash0a\)
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\backslash0ab\)
2
\(_1bbab\backslash0abb\)
5
\(_2bab\backslash0abba\)
4
\(_3ab\backslash0abbab\)
1
\(_4b\backslash0abbab\backslash0\)
3
\(_5\backslash0abbab\backslash0a\)
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\backslash0ab\)
2
\(_1bbab\backslash0abb\)
5
\(_2bab\backslash0abba\)
4
\(_3ab\backslash0abbab\)
1
\(_4b\backslash0abbab\backslash0\)
3
\(_5\backslash0abbab\backslash0a\)
0
統整一下
將每個字串往後抄
目前長度個字元
把兩段字串分別用\(rank\)值表示
弄出一個\(pair\)
利用\(pair\)排序
把新的\(rank\)抄回去
統整一下
將每個字串往後抄
目前長度個字元
把兩段字串分別用\(rank\)值表示
弄出一個\(pair\)
利用\(pair\)排序
把新的\(rank\)抄回去
總共要繞\(\log n\)圈
複雜度
將每個字串往後抄
目前長度個字元
把兩段字串分別用\(rank\)值表示
弄出一個\(pair\)
利用\(pair\)排序
把新的\(rank\)抄回去
總共要繞\(\log n\)圈
\(\Omicron(n)\)
\(\Omicron(n\log n)\)
\(\Omicron(n)\)
複雜度
將每個字串往後抄
目前長度個字元
把兩段字串分別用\(rank\)值表示
弄出一個\(pair\)
利用\(pair\)排序
把新的\(rank\)抄回去
總共要繞\(\log n\)圈
\(\Omicron(n)\)
\(\Omicron(n\log n)\)
\(\Omicron(n)\)
總複雜度\(\Omicron(n\log^2n)\)
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\}\)
\(\Rightarrow\{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\}\)
\(\Rightarrow\{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\}\)
\(\Rightarrow\{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\}\)
\(\Rightarrow\{50,41,61,72,43,46,57,78,38,59\}\)
\(\Rightarrow\{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\)抄回去
總共要繞\(\log n\)圈
\(\Omicron(n)\)
\(\Omicron(n\log n)\)
\(\Omicron(n)\)
複雜度
將每個字串往後抄
目前長度個字元
把兩段字串分別用\(rank\)值表示
弄出一個\(pair\)
利用\(pair\)排序
把新的\(rank\)抄回去
總共要繞\(\log n\)圈
\(\Omicron(n)\)
\(\Omicron(n)\)
\(\Omicron(n)\)
複雜度
將每個字串往後抄
目前長度個字元
把兩段字串分別用\(rank\)值表示
弄出一個\(pair\)
利用\(pair\)排序
把新的\(rank\)抄回去
總共要繞\(\log n\)圈
\(\Omicron(n)\)
\(\Omicron(n)\)
\(\Omicron(n)\)
總複雜度\(\Omicron(n\log n)\)
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\backslash0\)
\(S_s[0]=\backslash0\)
\(S_s[1]=b\backslash0\)
\(S_s[2]=ab\backslash0\)
\(S_s[3]=bab\backslash0\)
\(S_s[4]=bbab\backslash0\)
\(S_s[5]=abbab\backslash0\)
\(LCP_s(1,4)\)
\(=LCP(b,bbab)\)
\(=1\)
接下來的目標
生出一個資料結構,可以幫我們查詢任意兩組後綴的LCP
好啦我們還是比較喜歡\(cycle\)
接下來的目標
生出一個資料結構,可以幫我們查詢任意兩個\(cycle\)的LCP
接下來的目標
生出一個資料結構,可以幫我們查詢任意兩個\(cycle\)的LCP
\(s=abbab\backslash0\)
\(s.cycle(0)=abbab\backslash0\)
\(s.cycle(1)=bbab\backslash0a\)
\(s.cycle(2)=bab\backslash0ab\)
\(s.cycle(3)=ab\backslash0abb\)
\(s.cycle(4)=b\backslash0abba\)
\(s.cycle(5)=\backslash0abbab\)
\(LCP_s(4,1)\)
\(=LCP(b\backslash0abba,bbab\backslash0a)\)
\(=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)\)的第\(2^i\)個前綴是否相同
- 就去查\(rank[i][x],rank[i][y]\),如果數字相同就代表前綴相同
把所有\(rank\)存起來的話…
- 這代表我們可以二分搜前綴長度!
- 複雜度:\(\Omicron(\log n)\)每次查詢
- (不考慮建\(rank\)的複雜度)
把所有\(rank\)存起來的話…
- 這代表我們可以二分搜前綴長度!
- 複雜度:\(\Omicron(\log n)\)每次查詢
- (不考慮建\(rank\)的複雜度)
但是空間複雜度呢?
空間炸裂
讓我們換個方式
\(S_s[5]=\backslash0\)
\(S_s[3]=ab\backslash0\)
\(S_s[0]=abbab\backslash0\)
\(S_s[4]=b\backslash0\)
\(S_s[2]=bab\backslash0\)
\(S_s[1]=bbab\backslash0\)
\(s=abbab\)
我們開一個陣列\(l\)
使得\(l[i]=\)排名在第\(i\)的後綴和排名在第\(i+1\)的後綴做\(LCP\)
\(S_s[5]=\backslash0\)
\(S_s[3]=ab\backslash0\)
\(S_s[0]=abbab\backslash0\)
\(S_s[4]=b\backslash0\)
\(S_s[2]=bab\backslash0\)
\(S_s[1]=bbab\backslash0\)
\(s=abbab\)
我們開一個陣列\(l\)
使得\(l[i]=\)排名在第\(i\)的後綴和排名在第\(i+1\)的後綴做\(LCP\)
\(l[0]=0\)
\(l[1]=2\)
\(l[2]=0\)
\(l[3]=1\)
\(l[4]=1\)
\(S_s[5]=\backslash0\)
\(S_s[3]=ab\backslash0\)
\(S_s[0]=abbab\backslash0\)
\(S_s[4]=b\backslash0\)
\(S_s[2]=bab\backslash0\)
\(S_s[1]=bbab\backslash0\)
\(l[0]=0\)
\(l[1]=2\)
\(l[2]=0\)
\(l[3]=1\)
\(l[4]=1\)
如果我想要找\(LCP(S_s[3],S_s[2])\)
\(S_s[5]=\backslash0\)
\(S_s[3]=ab\backslash0\)
\(S_s[0]=abbab\backslash0\)
\(S_s[4]=b\backslash0\)
\(S_s[2]=bab\backslash0\)
\(S_s[1]=bbab\backslash0\)
\(l[0]=0\)
\(l[1]=2\)
\(l[2]=0\)
\(l[3]=1\)
\(l[4]=1\)
如果我想要找\(LCP(S_s[3],S_s[2])\)
答案會是\(\min(l[1\dots4])\)
找\(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(S_s[0],S_s[3])=2\)
\(\Rightarrow LCP(S_s[1],S_s[4])=1\)
\(\Rightarrow LCP(S_s[1],S_s[2])\geq1\)
\(s=abbab\)
\(_50\)
\(_3ab0\)
\(_0abbab0\)
\(_4b0\)
\(_2bab0\)
\(_1bbab0\)
\(l[0]=?\)
\(l[1]=2\)
\(l[2]=?\)
\(l[3]=?\)
\(l[4]\geq1\)
\(LCP(S_s[0],S_s[3])=2\)
\(\Rightarrow LCP(S_s[1],S_s[4])=1\)
\(\Rightarrow LCP(S_s[1],S_s[2])\geq1\)
\(s=abbab\)
\(_50\)
\(_3ab0\)
\(_0abbab0\)
\(_4b0\)
\(_2bab0\)
\(_1bbab0\)
\(l[0]=?\)
\(l[1]=2\)
\(l[2]=?\)
\(l[3]=?\)
\(l[4]\geq1\)
\(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(S_s[1],S_s[2])=1\)
\(\Rightarrow LCP(S_s[2],S_s[3])=0\)
\(\Rightarrow LCP(S_s[2],S_s[4])\geq0\)
\(s=abbab\)
\(_50\)
\(_3ab0\)
\(_0abbab0\)
\(_4b0\)
\(_2bab0\)
\(_1bbab0\)
\(l[0]=?\)
\(l[1]=2\)
\(l[2]=?\)
\(l[3]\geq0\)
\(l[4]=1\)
\(LCP(S_s[1],S_s[2])=1\)
\(\Rightarrow LCP(S_s[2],S_s[3])=0\)
\(\Rightarrow LCP(S_s[2],S_s[4])\geq0\)
\(s=abbab\)
\(_50\)
\(_3ab0\)
\(_0abbab0\)
\(_4b0\)
\(_2bab0\)
\(_1bbab0\)
\(l[0]=?\)
\(l[1]=2\)
\(l[2]=?\)
\(l[3]\geq0\)
\(l[4]=1\)
\(s=abbab\)
\(_50\)
\(_3ab0\)
\(_0abbab0\)
\(_4b0\)
\(_2bab0\)
\(_1bbab0\)
\(l[0]=?\)
\(l[1]=2\)
\(l[2]=?\)
\(l[3]\geq0\)
\(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(S_s[2],S_s[4])=1\)
\(\Rightarrow LCP(S_s[3],S_s[5])=0\)
\(\Rightarrow LCP(S_s[3],S_s[5])\geq0\)
\(s=abbab\)
\(_50\)
\(_3ab0\)
\(_0abbab0\)
\(_4b0\)
\(_2bab0\)
\(_1bbab0\)
\(l[0]\geq0\)
\(l[1]=2\)
\(l[2]=?\)
\(l[3]=1\)
\(l[4]=1\)
\(LCP(S_s[2],S_s[4])=1\)
\(\Rightarrow LCP(S_s[3],S_s[5])=0\)
\(\Rightarrow LCP(S_s[3],S_s[5])\geq0\)
\(s=abbab\)
\(_50\)
\(_3ab0\)
\(_0abbab0\)
\(_4b0\)
\(_2bab0\)
\(_1bbab0\)
\(l[0]\geq0\)
\(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
- 496