【笔记】字符串基础 2023.7.31

7.31 字符串

period & border

  • 字符串 \(s\) 的 period 是一个字符串,满足 \(s\) 是无限重复首尾相连的 period 的前缀。

  • 字符串 \(s\) 的 border 是一个字符串,是前缀等于后缀的子串,不能是原串。

  • 根据定义 \(\min period=n-\max border\)(就是循环相等相等相等)

  • 举例:abababa 的最短 period 是 ab,最长 border 是 ababa

exkmp 与 manacher 的对比

void exkmp(int len) {
	z[1] = len;
	for (int i = 2, l = 0, r = 0; i <= len; i++) {
		if (i <= r) z[i] = min(z[i - l + 1], r - i + 1);
		while (i + z[i] <= len && a[1 + z[i]] == a[i + z[i]]) ++z[i];
		if (i + z[i] - 1 > r) r = i + z[l = i] - 1;
	}
}
void manacher() {
	for (int i = 1, mid = 0, r = 0; i <= n; i++) {
		if (i <= r) pal[i] = min(pal[mid * 2 - i], r - i + 1);
		while (a[i - pal[i]] == a[i + pal[i]]) ++pal[i];
		if (i + pal[i] - 1 > r) r = i + pal[mid = i] - 1;
	}
}

都是想尽可能用前面的极大区间对称得到新的答案下界,并声称每次扩展都至少使极大区间右移。只有一些 corner 有不同。

BZOJ4974 字符串大师

模拟 KMP 即可,贪心填入字典序小的字符。

POJ2406 Power Strings

比上一个题的要求更加严格。跳 \(border\) 直到 \(period|n\) 即可。

[NOI2014] 动物园

维护指针指向不超过一半长度的串。

[NOIP2020] 字符串匹配(WIP)

枚举循环节 \(AB\),利用 EXKMP 找到最大循环次数。利用桶和指针可以做到 \(O(n)\)

Trie

LOJ537 DNA 序列

将“所有连续 \(k\) 个碱基形成的碱基序列”插入 Trie。

ACAM

[USACO 2015 Feb. Gold] Censoring

ACAM,但是撤销。

[NOI2011] 阿狸的打字机

建 ACAM,离线之后找到 \(y\) 在 Fail 树上的对应和 \(x\) 在 Trie 树上的对应。那么就是要数 \(y\) 的子树中有多少个 \(x\) 的对应,暴力 dfs 然后树状数组维护。

Manacher

就是暴力!维护 \(mid,maxright(mr)\) 表示现在知道的最长回文串,新的 \(i\),若 \(mid<i<mr\) 那么从 \(i\) 关于 \(mid\) 的对称点那边找个信息从那里开始暴力,否则从 \(1\) 开始暴力。

P4555 [国家集训队] 最长双回文串

枚举分界点。

PAM

性质:对于一个回文串 \(s\),它有一段后缀 \(t\),满足:(这里 border 不一定是最长的)

  • 如果 \(t\) 是 border,那么 \(t\) 是回文串(正着读和反着读确实一样)
  • 如果 \(t\) 是回文串,那么 \(t\) 是 border(因为正着读和反着读一样)

每个节点表示一个回文串,它的转移边是在它前后加上字符,fail 指针是它的最长 border。

插入字符时增量构造,先跳 fail,找到合法的可以转移的地方并插入,然后再跳一次 fail 求出它的 fail,它的 fail 必须有个字符是插入字符。

[SHOI2011] 双倍回文

用 PAM 维护一下长度为 \(len/2\) 的串所在节点即可。

P4762 [CERC2014] Virus synthesis

一样维护指针后在 PAM 上 dp 即可。转移分为从 border 处转移以及向 nxt 转移。

CF932G Palindrome Partition

首先偶数位字符串按顺序翻转回去,变成偶回文划分。

Weak periodicity lemma:若 \(p,q\) 都是字符串 \(s\) 的 period,\(p+q\leq |s|+1\),则 \(\gcd(p,q)\) 也是 period。

所以,一个串的所有 border 构成 \(O(\log n)\) 个等差数列。大力 DP。

杂题

CF981A Non-palindrome String

输入一个串 \(s\),要求判断 \(s\) 是否有非回文子串。如果有,则输出 \(s\) 的任意一个最长非回文子串。

  • 如果这个字符串每个字符相同,不存在非回文子串。
  • 如果这个字符串不是回文,输出字符串长度。
  • 如果这个字符串是回文,输出这个字符串长度 \(-1\)

最小表示法

维护三个指针 \(i, j, k\)\(i, j\) 表示当前正在比较 \(i, j\) 位置的位移,\(k\) 表示比较到哪一位。如果当前 \(k\) 位置不一样,不妨设 \(S_i < S_j\)。那么发现可以将 \(j\) 加上 \(k\)\(j\) 丢失的答案在 \(i\) 中统计。

SA

for(int i=1; i<=n; ++i) ++cnt[rk[i]];
for(int i=1; i<=lim; ++i) cnt[i]+=cnt[i-1];
for(int i=1; i<=n; ++i) rid[i]=rk[id[i]];//优化 CPU 缓存命中的写法
for(int i=n; i; --i) sa[cnt[rid[i]]--]=id[i];

[AHOI2013] 差异

\(len\) 可以拎出来算,\(-2\) 不管,\(LCP(T_l,T_r)=\min_{l<i\leq r}hei_i\)。枚举 \(hei_i\),单调栈求出它向左向右管辖多少个。或者建笛卡尔树,管辖区间就是左右两边横跨它的个数。

[NOI2016] 优秀的拆分(WIP)

处理以 \(i\) 结尾的 \(AA\) 的个数和以 \(i\) 开头的 \(BB\) 的个数,答案等于 \(\sum pre[i]suf[i+1]\)

枚举 \(A\) 的长度 \(len\),按照每 \(len\) 个取关键点,那么长为 \(2len\)\(AA\) 必然跨过两个关键点。对于一个关键点,求出它和下一个的 LCP/LCS,然后搞出所有 \(A\) 差分加上去之类的。调和级数 \(O(n\log n)\)

P5028 Annihilate

将串用特殊符号(#)连接,建后缀数组,从一个点开始单调栈,注意区分颜色不同。

[NOI2015] 品酒大会

后缀数组,建笛卡尔树/单调栈搞一下即可。

BZOJ4278 [ONTAK2015] Tasowanie

按照 rnk 贪心。

CodeChef Very Long Suffix Array

考虑 \(n\leq 10^5\) 怎么做。根据后缀数组定义,必然有 \(s[a_1:n]<s[a_2:n]<\cdots<s[a_n:n]\),注意这里是后缀而且没有相等。我们再考虑字符,\(s[a_1]\leq s[a_2]\leq\cdots\leq s[a_n]\),这个字符就能相等。我假设 \(s[a_i]=s[a_{i+1}]\),那么说明 \(s[a_i+1:n]<s[a_{i+1}+1:n]\),判断一下这个事实是否成立,如果不成立说明 \(s[a_i]<s[a_{i+1}]\),否则两者都可以。如果我们求出有多少个 \(i\) 满足 \(s[a_i]\)\(s[a_{i+1}]\) 既能相等也能小于,那么答案乘 \(2\)

考虑现在 \(n\leq 10^9\),那么实际上我们的连续段的答案是好做的,边界只有 \(O(m)\) 个,剩下的就是平衡树了。

posted @ 2023-08-01 08:29  caijianhong  阅读(25)  评论(0编辑  收藏  举报