classTrie { using Node = std::array<int, 26>; std::vector<Node> nxt; // 0 表示没有,1 表示有且没被访问过,2 表示有且被访问过 std::vector<int> tag; voidaddNode(int fa, int c){ nxt[fa][c] = nxt.size(); nxt.emplace_back(Node()); tag.emplace_back(0); } public: Trie() : nxt(1), tag(1) {} voidinsert(std::string s){ int p = 0; for (auto x : s) { int c = x - 'a'; if (nxt[p][c] == 0) addNode(p, c); p = nxt[p][c]; } tag[p] = 1; } intfind(std::string s){ int p = 0; for (auto x : s) { int c = x - 'a'; if (nxt[p][c] == 0) return0; p = nxt[p][c]; } if (tag[p] != 1) return tag[p]; tag[p] = 2; return1; } };
classTrie { using Node = std::array<int, 2>; std::vector<Node> ch; voidaddNode(int fa, int c){ ch[fa][c] = ch.size(); ch.emplace_back(Node()); } public: Trie() : ch(1) {} voidinsert(int x){ for (int i = 30, p = 0; i >= 0; --i) { int c = (x >> i) & 1; if (ch[p][c] == 0) addNode(p, c); p = ch[p][c]; } } intgetMax(int x){ int r = 0; for (int i = 30, p = 0; i >= 0; --i) { int c = (x >> i) & 1; if (ch[p][c ^ 1]) { p = ch[p][c ^ 1]; r |= (1 << i); } else { p = ch[p][c]; } } return r; } intgetAns(const std::vector<int> &a){ int r = 0; for (auto x : a) { insert(x); r = std::max(r, getMax(x)); } return r; } };
std::vector<int> prefixFunction(std::string s){ int n = s.size(); std::vector<int> p(n); for (int i = 1; i < n; ++i) { int j = p[i - 1]; while (j > 0 && s[i] != s[j]) j = p[j - 1]; if (s[i] == s[j]) ++j; p[i] = j; } return p; }
注意上述算法是一个在线算法,即可以一个一个的字符添加。
KMP 算法(Knuth-Morris-Pratt 算法)
给定文本 t 和 字符串 s,尝试找到并展示 s 在 t 中的所有出现。
我们可以构建一个字符串 s + # + t,然后求前缀函数即可, 并且注意
函数值最大为 n
若值为 n 表示匹配成功,且 i - 2n 为出现位置
我们不需要保存 t 的信息。
因此我们可以在 \(O(n + m)\) 时间 \(O(n)\) 空间利用前缀函数解决此问题。
1 2 3 4 5 6 7 8 9 10 11 12
// 返回所有匹配在 t 的首位置 std::vector<int> kmp(std::string s, std::string t){ std::vector<int> ans; int n = s.size(), m = t.size(); if (n > m) return ans; auto p = prefixFunction(s); for (int i = 0, j = 0; i < m; ++i) { while (j > 0 && s[j] != t[i]) j = p[j - 1]; if (s[j] == t[i] && ++j == n) ans.emplace_back(i - n + 1); } return ans; }
// 返回 长度为 i 的前缀出现的次数 std::vector<int> countPrefix(std::string s){ auto p = prefixFunction(s); int n = s.size(); std::vector<int> ans(n + 1); for (auto x : p) ++ans[x]; for (int i = n - 1; i > 0; --i) ans[p[i - 1]] += ans[i]; for (int i = 0; i <= n; ++i) ++ans[i]; return ans; } // 返回 s 长度为 i 的前缀在 t 中出现的次数 std::vector<int> countPrefix(std::string s, std::string t){ auto p = prefixFunction(s); int n = s.size(), m = t.size(); std::vector<int> ans(n + 1); for (int i = 0, j = 0; i < m; ++i) { while (j > 0 && t[i] != s[j]) j = p[j - 1]; if (t[i] == s[j]) ++j; ++ans[j]; } ++ans[0]; for (int i = n; i > 0; --i) ans[p[i - 1]] += ans[i]; return ans; }
\(z[i]\) 表示 s 和 \(s[i, n - 1]\) 的最长公共前缀,约定 \(z[0] = 0\)
我们称 \(i, i + z[i] - 1\) 是 i 的匹配段,也称 z-Box。
维护右端点最大的匹配段,记作 \([l, r]\),即 \(s[l, r]\) 是 s 的前缀。
首先初始化,\(l = r = 0, i = 1\)(始终保证 \(l \leq i\))
若 \(i \leq r\),根据定义 \(s[i, r] = s[i - l, r - l]\),若 \(s[i - l] < r - i + 1\),则 \(z[i] = z[i - l]\),反之,令 z[i] = r - i + 1 然后暴力拓展直到不能拓展为止
若 \(i > r\),那我们直接暴力求出 \(z[i]\),
无论那种情况都要更新 r。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
std::vector<int> zFunction(std::string s){ int n = s.size(); std::vector<int> z(n); for (int i = 1, l = 0, r = 0; i < n; ++i) { if (i <= r && z[i - l] < r - i + 1) { z[i] = z[i - l]; } else { int j = std::max(0, r - i + 1); while (i + j < n && s[j] == s[i + j]) ++j; z[i] = j; if (i + j - 1 > r) { l = i; r = i + j - 1; } } } return z; }
复杂度,每次拓展 r 增加 1,因此总拓展次数小于 n,所以整体复杂度 \(O(n)\)。
类似于前缀函数,我们也可以求 KMP
KMP
构造 s + # + t 的串,那么我们可以通过计算 s 的 Z-函数,然后,在 t 中也类似的做,然后如果 t 中找到了长度为 s 长度的 z 值,那么就相当于匹配到了,并且注意到我们在这里不会再更新 r 值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// 返回所有匹配在 t 的首位置 std::vector<int> kmp(std::string s, std::string t){ std::vector<int> ans; int n = s.size(), m = t.size(); if (n > m) return ans; auto z = zFunction(s); for (int i = 0, l = 0, r = -1; i < m; ++i) { if (i > r || z[i - l] >= r - i + 1) { int j = std::max(0, r - i + 1); while (j < n && i + j < m && s[j] == t[i + j]) ++j; if (j == n) ans.emplace_back(i); if (i + j - 1 > r) { l = i; r = i + j - 1; } } } return ans; }
// 请确保最后一个元素为 0,且 a 中其它元素都是正整数,且最大值较小。 std::vector<int> SAIS(std::vector<int> a){ enumTYPE {L, S}; int n = a.size() - 1, mx = *std::max_element(a.begin(), a.end()) + 1; std::vector<int> SA(n + 1, -1); std::vector<int> bucket(mx), lbucket(mx), sbucket(mx); for (auto x : a) ++bucket[x]; for (int i = 1; i < mx; ++i) { bucket[i] += bucket[i - 1]; lbucket[i] = bucket[i - 1]; sbucket[i] = bucket[i] - 1; } // 确定 L, S 类型以及 * 型的位置 std::vector<TYPE> type(n + 1); type[n] = S; for (int i = n - 1; i >= 0; --i) { type[i] = (a[i] < a[i + 1] ? S : (a[i] > a[i + 1] ? L : type[i + 1])); } // 诱导排序(从 * 型诱导到 L 型、从 L 型诱导到 S 型) auto inducedSort = [&]() { for (int i = 0; i <= n; ++i) { if (SA[i] > 0 && type[SA[i] - 1] == L) { SA[lbucket[a[SA[i] - 1]]++] = SA[i] - 1; } } for (int i = 1; i < mx; ++i) { sbucket[i] = bucket[i] - 1; } for (int i = n; i >= 0; --i) { if (SA[i] > 0 && type[SA[i] - 1] == S) { SA[sbucket[a[SA[i] - 1]]--] = SA[i] - 1; } } }; // 首先根据诱导排序给出 LMS-prefix 的排序 std::vector<int> pos; for (int i = 1; i <= n; ++i) { if (type[i] == S && type[i - 1] == L) { pos.emplace_back(i); } } for (auto x : pos) SA[sbucket[a[x]]--] = x; inducedSort(); // 根据 LMS-prefix 的排序给出 LMS-substring 的命名,即得到 S1 auto isLMSchar = [&](int i) { return i > 0 && type[i] == S && type[i - 1] == L; }; auto equalSubstring = [&](int x, int y) { do { if (a[x] != a[y]) returnfalse; ++x; ++y; } while (!isLMSchar(x) && !isLMSchar(y)); return a[x] == a[y]; }; // 注意到因为 LMS-prefix 排序会导致仅有相邻的 LMS-substring 才可能相等 std::vector<int> name(n + 1, -1); int lx = -1, cnt = 0; bool flag = true; // 表示无相同的 LMS-substring for (auto x : SA) if (isLMSchar(x)) { if (lx >= 0 && !equalSubstring(lx, x)) ++cnt; if (lx >= 0 && cnt == name[lx]) flag = false; name[x] = cnt; lx = x; } std::vector<int> S1; for (auto x : name) if (x != -1) S1.emplace_back(x); auto getSA1 = [&]() { int n1 = S1.size(); std::vector<int> SA1(n1); for (int i = 0; i < n1; ++i) SA1[S1[i]] = i; return SA1; }; auto SA1 = flag ? getSA1() : SAIS(S1); // 再次诱导排序,根据 S1 的排序得到 SA lbucket[0] = sbucket[0] = 0; for (int i = 1; i < mx; ++i) { lbucket[i] = bucket[i - 1]; sbucket[i] = bucket[i] - 1; } std::fill(SA.begin(), SA.end(), -1); // 这里是逆序扫描 SA1,因为 SA 中 S 型桶是倒序的 for (int i = SA1.size() - 1; i >= 0; --i) { SA[sbucket[a[pos[SA1[i]]]]--] = pos[SA1[i]]; } inducedSort(); return SA; }
std::vector<int> SAIS(const std::string &s){ // s 的字符集为小写字,则可使用下面函数。 // auto f = [](char x) -> int { return int(x - 'a') + 1;}; auto f = [](char x) -> int { returnint(x) + 1;}; std::vector<int> a; for (auto c : s) a.emplace_back(f(c)); a.emplace_back(0); auto sa = SAIS(a); return std::vector<int>(sa.begin() + 1, sa.end()); }
找在 T 中子串 S
注意这里可以先给定 T,再一个个给 S。而 KMP,AC 自动机都是先给 S 再给 T。
若 S 是 T 的子串,那么 S 必然是 T 的后缀的前缀。因为后缀已经被排序了,因此我们可以二分解决。因此复杂度为 \(O(|S| \log|T|)\),出现次数也可以通过二分得到,并且输出位置也很轻松哈哈。
这个算法具体说就是让 \(s = w \cdots w \hat{w} x\),其中 w 是 Lyndon word,\(\hat{w}\) 是 w 的前缀,\(x\) 是还未考虑到的剩余部分。注意到此时我们还不能说 w 是 s 的 Lyndon 分解的一部分,因为如果 \(\hat{w} x[0] > w\)(此时就不是了,此时就变成了一个新的 Lyndon word)。而如果 \(\hat{w} x[0]\) 还是 w 的前缀,那就把它吸收进去,继续考虑。如果 \(\hat{w} x[0] < w\),那么此时 w 确实是 Lyndon 分解的一部分,把这些 w 全踢出去即可。
// Lyndon decomposition using Duval algorithm std::vector<std::string> duval(const std::string &s){ std::vector<std::string> r; int n = s.size(), i = 0; while (i < n) { int j = i + 1, k = i; while (j < n && s[k] <= s[j]) { if (s[k] < s[j]) k = i; else ++k; ++j; } while (i <= k) { r.emplace_back(s.substr(i, j - k)); i += j - k; } } return r; }
#include<bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = longlong;
// 仅仅处理奇数长回文串,这个实现像极了 Z-函数 std::vector<int> Manacher(std::string s){ int n = s.size(); std::vector<int> d(n); for (int i = 0, l = 0, r = -1; i < n; ++i) { int k = i > r ? 1 : std::min(d[l + r - i], r - i); while (k <= i && i + k < n && s[i - k] == s[i + k]) ++k; d[i] = k--; if (i + k > r) { l = i - k; r = i + k; } } return d; }
intmain(){ //freopen("in", "r", stdin); std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::string s; while (std::cin >> s) { std::string ss("#"); std::swap(ss, s); for (auto x : ss) { s += x; s += '#'; } auto d = Manacher(s); int now = 0; while (now < s.size() && now + d[now] != s.size()) ++now; std::cout << ss; for (int i = now - d[now]; i >= 0; --i) if (s[i] != '#') std::cout << s[i]; std::cout << "\n"; } return0; }
#include<bits/stdc++.h> #define watch(x) std::cout << (#x) << " is " << (x) << std::endl using LL = longlong;
// 仅仅处理奇数长回文串 std::vector<int> Manacher(std::string s){ int n = s.size(); std::vector<int> d(n); for (int i = 0, l = 0, r = -1; i < n; ++i) { int k = i > r ? 1 : std::min(d[l + r - i], r - i); while (k <= i && i + k < n && s[i - k] == s[i + k]) ++k; d[i] = k--; if (i + k > r) { l = i - k; r = i + k; } } return d; }
intmain(){ //freopen("in", "r", stdin); std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::string s; std::cin >> s; std::string ss("#"); std::swap(ss, s); for (auto x : ss) { s += x; s += '#'; } auto d = Manacher(s); int n = s.size(); std::vector<int> l(n), r(n); auto cmax = [](int &x, int y) { if (x < y) x = y; }; for (int i = 0; i < n; ++i) { cmax(l[i + d[i] - 1], d[i] - 1); cmax(r[i - d[i] + 1], d[i] - 1); } for (int i = n - 3; i >= 0; i -= 2) cmax(l[i], l[i + 2] - 2); for (int i = 2; i < n; i += 2) cmax(r[i], r[i - 2] - 2); int ans = 0; for (int i = 2; i < n; i += 2) if (l[i] && r[i]) cmax(ans, l[i] + r[i]); std::cout << ans << "\n"; return0; }