【笔记】字符串基础
7.31 字符串
(ex)KMP
不会 exkmp,标记了。exkmp 就是暴力。
-
字符串 \(s\) 的 period 是一个字符串,满足 \(s\) 是无限重复首尾相连的 period 的前缀。
-
字符串 \(s\) 的 border 是一个字符串,是前缀等于后缀的子串,不能是原串。
-
根据定义 \(\min period=n-\max border\)(就是循环相等相等相等)
-
举例:
abababa
的最短 period 是ab
,最长 border 是ababa
。
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)\) 个,剩下的就是平衡树了。