z 函数
定义
对于字符串 \(S\),令 \(z_i=\text{lcp}(S,\text{ssf}(S,i))\),\(z_1\) 一般设为 \(0\) 或 \(n\)
也称为扩展 \(\text{KMP}\)
模板
namespace string_algo {
// z 函数
template <typename value_t, typename output_iter_t, ass_is_RAI(output_iter_t)>
void z_function(const basic_string<value_t> &s, output_iter_t z){
if (s.empty())return;
size_t n = s.size();
*z = 0;
for (size_t i = 1, l = 0, r = 1; i != n; ++i){//[l,r)
if (i < r && z[i - l] < r - i)z[i] = z[i - l];
else {
if (i < r)z[i] = r - i; else z[i] = 0;
while (i + z[i] < n && s[z[i]] == s[i + z[i]])++z[i];
}
if (i + z[i] > r)l = i, r = i + z[i];
}
}
}
时间复杂度 \(O(n)\)
模板题:P5410 【模板】扩展 KMP/exKMP(Z 函数) \(\quad\) 代码
匹配子串
要找到字符串 \(p\) 在字符串 \(t\) 中所有出现的位置,构造 \(s=p+\square+t\),其中 \(\square\) 为一个在 \(t\) 和 \(p\) 中都没有出现过的字符,计算出 \(s\) 的 \(z\) 函数 \(z_{1\sim |s|}\),若 \(z_{|p|+1+i}=|p|\),则 \(t_{[i,i+|p|)}\) 为 \(p\) 的一次出现
时空复杂度为 \(O(|p|+|t|)\)
字符串整周期
对于给定的字符串 \(s\),求出最短的字符串 \(t\) 使得存在正整数 \(k\) 令 \(s=t^k\)
\(|t|\) 为最小的 \(i\) 使得 \(i+z_i=|s|\)
时空复杂度 \(O(|s|)\)
例 1:P7114 [NOIP2020] 字符串匹配
给定 \(S_{1\sim n}\),求出有多少方法将其表示为 \(S=(AB)^kC\) 的形式,满足 \(f(A)\le f(C)\),其中 \(i\ge 1\),\(A,B,C\) 为非空字符串,\(f(S)\) 表示 \(S\) 中出现奇数次字符的数量,\(n\le 2^{20}\),\(S_i\) 为小写字母,多测 \(T\le5\)
考虑枚举 \(|AB|\)
若 \(i\) 为奇数,则 \(f(C)=f(\text{sff}(S,|AB|+1))\),否则 \(f(C)=f(S)\)
由于 \(1\le i\le\left\lfloor\frac{\min(z_{|AB|},n-|AB|+1)}{|AB|}\right\rfloor+1\)(\(z_{|AB|}\) 是因为需要有长为 \(|AB|\) 的整周期,\(n-|AB|+1\) 是因为 \(C\) 非空)
可以算出合法的 \(i\) 的范围中,\(i\) 为奇数的数量 \(Ct_1\) 和为偶数的数量 \(Ct_0\),容易 \(O(1)\) 处理
令 \(Fsf_i=f(\text{sff}(i))\),\(Fpr_i=f(\text{prf}(i))\),容易 \(O(n)\) 递推处理
则一个 \(|AB|\) 的贡献为 \(Ct_0\cdot Ft(Fsf_1)+Ct_1\cdot Ft(Fsf_{|AB|+1})\),其中 \(Ft(x)=\sum_{A=1}^{|AB|-1}[Fpr_A\le x]\)
令 \(Vl(x)=Ft(x)-Ft(x-1)\)(\(Vl(0)=Ft(0)\)),则枚举的 \(|AB|\) 加一时,会令一个 \(Vl(k)\) 加一,且每次查询为 \(Vl\) 的前缀和
容易使用树状数组维护(显然 \(Fpr(x)\le V\),其中 \(V\) 为字符集大小 \(26\))
时间复杂度 \(O(\sum n\log V)\)
例 2:[ARC058F] 文字列大好きいろはちゃん
\(n\) 个字符串 \(s_{1\sim n}\),选若干个按给出顺序连接,总长必须等于 \(k\),求字典序最小的,保证有解,\(1\le n\le 2000,k\le10^4,\sum |s_i|\le10^6\),时限 \(5s\)
考虑 \(dp\)
令 \(f_{i,j}\) 表示 \(s_{1\sim i}\) 中选出总长为 \(j\) 的字典序最小字符串(即 \(f_{i,j}\) 值为一个长为 \(j\) 的字符串)
则 \(f_{i,j}=\min(f_{i-1,j},f_{i-1,j-|s_i|}+s_i)\)(其中 \(\min\) 为取字典序较小的,\(+\) 表示字符串拼接)
显然其中一部分 \(f_{i,j}\) 对答案无用(具体定义之后讲)
先 \(0/1\) 背包求出 \(g_{i,j}\),表示 \(\text{sff}(S,i)\) 中是否可能选出长为 \(j\) 的字符串
则 \(g_{i+1,k-j}=0\) 的 \(f_{i,j}\) 为无用的
对于同一个 \(i\),若存在 \(j<k\) 且 \(f_{i,j}\) 不为 \(f_{i,k}\) 前缀,则字典序较大的一个一定不优,若两者对应的 \(g\) 都不为 \(0\),则显然字典序较大的一个无用
因此对于同一 \(i\),其所有有用的 \(f_{i,j}\) 一定为 \(j\) 最大的一个的前缀
即令 \(res_i\) 为 \(j\) 最大的一个有用的 \(f_{i,j}\),则有用的 \(f_{i,k}=\text{prf}(res_i,k)\)
这样空间复杂度降到 \(O(nk)\)
从 \(i-1\) 转移到 \(i\),考虑维护一个单调栈,栈中保存有用的 \(f_{i,j}\),且下方的为上方的前缀
从小到大枚举 \(j\)
若新加入一个 \(f_{i,j}\)(需要保证其满足对应的 \(g\) 不为 \(0\),且 \(f_{i-1,j}\) 和 \(f_{i-1,j-|s_i|}\) 至少有一个有用,此时 \(f_{i,j}\) 可通过 \(f_{i-1,j}\) 和 \(f_{i-1,j-|s_i|}\) 求出),则有三种情况:
- 栈空则直接压入栈
- 栈顶的 \(f_{i,j}\) 字典序小于当前处理的 \(f_{i,j}\),则当前 \(f_{i,j}\) 无用
- 否则一直弹栈(也可能不需要弹)直到栈空或栈顶的 \(f_{i,j}\) 为当前处理 \(f_{i,j}\) 的前缀,然后将其压入栈
计算完一个 \(i\) 后,栈顶即为其 \(res\)
最终 \(res_n\) 即为答案
这样 暴力计算 可以求出所有有用的 \(f_{i,j}\),虽然时间复杂度和直接计算相同,但可以进一步优化
瓶颈在于字符串比较,考虑如何优化
发现每次为 \(\text{prf}(res_{i-1},j)[+s_i]\) 和 \(\text{prf}(res_{i-1},k)[+s_i]\) 比较的形式
经过分类讨论,都可以转化为 \(res_{i-1}\) 或 \(s_i\) 的子串 与 \(s_i\) 的前缀的比较
若将 \(s_i\) 和 \(res_{i-1}\) 拼接起来,则两者合并为拼接后字符串的子串和其前缀的比较
这可以通过 \(z\) 函数实现
这样总时间复杂度为 \(O(nk+\sum |s_i|)\),常数极大,空间复杂度应该可以滚动数组优化到 \(O(k+\max s_i)\)

浙公网安备 33010602011771号