字符串

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\)。

posted @ 2026-09-30 07:56  RainyRadio  阅读(9)  评论(0)    收藏  举报