字符串

字符串

本文旨在复习字符串算法,包含一些传统算法和神秘乱搞,不涉及 border 论与 Lyndon。

字符串哈希

生日悖论指出,选取的映射集合大小应该超过所需判断集合 \(S\)\(|S|^2\) 量级。

对于单哈希选取的质数 \(p\),其能处理的数据规模应不超过 \(\sqrt p\)

双哈希是卡不掉的,可以放心使用双哈希。

KMP 与 AC 机

对于 KMP 算法,个人喜欢的理解是求前缀 \([1,i]\) 的 boder,记其长度为 \(\text{fail}_i\)

int n;
char s[maxn];
//对于 s[1,n] 做 KMP。
void kmp(){
	int i=2,j=1;
    while(i<=n){
		if(s[i]==s[j])fail[i]=j,i++,j++;
        else{
			if(j==1)i++;
            else j=fail[j-1]+1;
        }
    }
    return ;
}

border 理论中一点基础的结论是:

  1. border 结构呈树状,祖先是后代的 border。
  2. 一个串的最短 border 长度不会超过串长的一半。

\(i\)\(s_{i+1}\)\(i+1\) 的转移与失配的转移的并为 KMP 自动机。

最基础的应用是处理模板串在文本串中的出现次数。
使用 KMP 自动机作为 DP 状态是一种常见的技巧,可以处理字符串出现次数一类的 DP 问题。

一个有趣的问题是将 KMP 可持久化:
一个经典的做法是将 KMP 稍加改进,使得其跳失配树有严格的保证。

具体的,如果 \(fail_j>\dfrac{j}{2}\),这说明串有一个较短的周期,我们直接跳过所有整周期,令 \(j:=(j-1)\bmod(j-fail_j)+1\)
否则我们直接跳 \(fail_j\) 就行了,保证了对于单个 \(i\)\(j\) 的移动次数不超过 \(\log n\)

一些练习:

[HNOI2019]JOJO

[CTS2019] 重复

[ICPC2017 WF]Tarot Sham Boast

07.04 youl


对于 AC 机,就是多个模式串的 KMP 自动机结构。定义 \(\text{fail}_k\) 为 trie 树上 \(k\) 节点的最长真后缀在 trie 树上的对应节点。

void pre(){
	for(int i=0;i<26;i++)if(t[0][i])q.push(t[0][i]);
	while(!q.empty()){
		int k=q.front();
		q.pop();
		for(int i=0;i<26;i++){
			int tmp=t[k][i];
			if(tmp)fail[tmp]=t[fail[k]][i],q.push(tmp);
			else t[k][i]=t[fail[k]][i];
		}
	}
	return ;
}

一些多模式串的问题不要一来就直接广义 SAM,AC 机是一个很好的选择。

值得一提的是 AC 机对于字符集较大的情况需要使用主席树维护出边,即 \(t(k,i)\)

AC 机的结构经常需要使用树上数据结构维护一些修改查询操作,但是 border 树上少见?

AC 机同样可以用于 DP 状态。

一些练习:

Modest Substrings

Manacher 与扩展 KMP

因为扩展 KMP 好像主要是直接使用板子得到的 Z 函数,而 Manacher 和扩展 KMP 几乎没有区别,就放一起吧。

//s[1,n]
//p[i] 表示位置 i 的最长回文半径(包含 i)。
for(int i=1;i<=2*n-1;i+=2)t[i]=s[i/2];
for(int i=0;i<=2*n;i+=2)t[i]='?';
int mid=0,r=0;
for(int i=1;i<=2*n;i++){
	if(i<=r)p[i]=min(p[2*mid-i],r-i+1);
	else p[i]=1;
	while(i-p[i]>=0&&i+p[i]<=2*n&&t[i-p[i]]==t[i+p[i]])p[i]++;
	if(i+p[i]>r)mid=i,r=i+p[i]-1;
	ans=max(ans,p[i]-1);
}
//z[i] 表示 LCP(a[1,n],a[i,n])。
z[1]=n;
int l=0,r=0;
for(register int i=2;i<=n;i++){
	if(i<=r)z[i]=fmin(z[i-l+1],r-i+1);
	while(i+z[i]<=n&&a[i+z[i]]==a[z[i]+1])z[i]++;
	if(i+z[i]-1>r)l=i,r=i+z[i]-1;
	ans1^=1ll*i*(z[i]+1);
}

时间复杂度分析都是 \(r\) 递增,复杂度线性。

PAM

不是很会。

bitset

bitset 做字符串匹配实在是太神秘了!!1

假设文本串为 \(s\)
核心的想法是对于字符集中的每一个字符 \(c\),开一个大小为 \(|s|\) 的 bitset \(T_c\),记录 \(c\) 出现在 \(s\) 中的哪些位置。

查询 \(t\)\(s\) 中的出现的开头位置可以考虑使用一个大小为 \(|s|\) 的 bitset \(M\),初始为全 1,对于 \(t\) 的每个位置 \(i\),将 \(M\)\(T_{t_i}\) 右移 \(i-1\) 位取交,最终得到的 \(M\) 即为所有开头位置。

一个巨大的优势是可以低复杂度修改文本串。

时间复杂度 \(O(\dfrac{|t||s|}{w})\)

SAM

不会考。

其它练习

[SDOI2018]反回文串

posted @ 2023-07-07 18:25  juju527  阅读(87)  评论(0编辑  收藏  举报