字符串
Trie
用来解决有关前缀的问题。经典的应用是最大异或。当空间较大时使用 map 可以有效压缩空间。
Manacher
核心思想是,维护当前最靠右的回文端点。
维护当前最靠右的回文端点 \(r\),对应的中心 \(j\),即 \([2j-r,r]\)。对于现在在计算的 \(i\):
- \(i > r\)。没招,没有信息可以参考,暴力扩展。
- \(i \le r\)。找到 \(i\) 的对称点 \(i'\),那么 \([2j-r,i']\) 即 \([i,r]\) 可以当作一个合法区间。\(p_i = r-i\) 后暴力即可。
cin >> T; S += '$'; S += '#';
for (ll i = 0; i < T.size(); ++ i )
S += T[i], S += '#';
n = S.size(); S = ' ' + S;
ll r = 0, j = 0;
for (ll i = 1; i <= n; ++ i ) {
p[i] = r > i ? min(p[2 * j - i], r - i) : 1;
while (S[i + p[i]] == S[i - p[i]]) ++ p[i];
if (i + p[i] > r)
r = i + p[i], j = i;
}
ll ans = 0;
for (ll i = 1; i <= n; ++ i )
ans = max(ans, p[i] - 1);
cout << ans << '\n';
kmp
思想是维护每个点的失配位置,即 border。
cin >> S >> T; m = T.size();
S = T + '#' + S;
n = S.size(); S = ' ' + S;
nt[1] = 0;
for (ll i = 2, j = 0; i <= n; ++ i ) {
while (j && S[i] != S[j + 1]) j = nt[j];
if (S[i] == S[j + 1]) ++ j;
nt[i] = j;
}
for (ll i = m + 2; i <= n; ++ i ) {
if (nt[i] == m) {
cout << i - 2 * m << '\n';
// i - m - 1 - m + 1
}
}
for (ll i = 1; i <= m; ++ i )
cout << nt[i] << ' '; cout << '\n';
失配树
注意到,你 \(i \to nt_i\) 连条边,会产生一棵树。可以做到一些很好的操作。具体参考动物园。
Z 函数 / exkmp
求解每个后缀与原串的 lcp。
杂题
CF1137B
简单题。考虑求出 t 串的最长 border,这个可以重复贡献。简单构造。
CF1109B
分讨太恶心了。来点无脑做法。手玩容易得到答案 \(\le 2\)。
\(k = 1\),枚举断点即可。
\(k=2\),找一个 border 等于 \(0\) 的前缀,及其对称点即可。
P5446
考虑一次翻转就得到 \(S\) 的情况。需要满足 \(S[i,n] = S[i, i - (n-i)]\) 注意后者是倒序。这需要 \(2i - 1 \ge n\)。
否则无法一次翻转,需要 \(i\) 翻转一次后合法,即 \(2i - 1\) 合法。且当前翻转合法,即 \(S[1,i] = S[i + (i-1), i]\)。
判断字符串相等使用哈希。
P2375
显然可以建出失配树上在上面倍增,但这样会带个 \(\log\)。显然可以每次跳 \(nt_j\),直到跳到 \(nt_j \le \frac{i}{2}\)。注意每次不用重置指针 \(j\)。
浙公网安备 33010602011771号