发布于 

字符串知识点复习

KMP

可爱的初级字符串算法。是某钻石教练会讲的算法。复杂度线性。

前缀数组(nxt)

定义: 代表 子串 最长的相等的真前缀真后缀的长度(字符串下标从 开始)。规定

可以这样理解: 的意思是将字符串整体向右挪动,当 到达 的位置时,第一次出现前缀与原位置完全吻合。这样,当 位失配时,我们只需要将 挪到 的位置,保证前 位都是正确的,更改第 位。

可爱的实现
1
2
3
4
5
6
7
8
void make(){
for(int i=2;i<=lx;i++){
int k=nxt[i-1];
while(k&&x[k+1]!=x[i]) k=nxt[k];
if(x[k+1]==x[i]) k++;
nxt[i]=k;
}
}

匹配操作

给定一个文本串和一个模式串,我们尝试找到并展示模式串在文本串中的所有出现。

  • 双指针逐位进行匹配,如果失配则跳向
可爱的实现
1
2
3
4
5
6
7
8
void work(){
int tx=0;//当前位置能匹配多少位,其实和k是一样的
for(int i=1;i<=ly;i++){
while(tx&&x[tx+1]!=y[i]) tx=nxt[tx];
if(x[tx+1] == y[i]) tx++;
if(tx == lx) printf("%d\n",i-lx+1);
}
}

Manacher

高妙的回文串检索算法。复杂度线性。思想是减少已知的判断,类似 KMP。

小但神奇的点:首先先在任意两个字符之间以及头尾字符两侧插入一个#。然后,你将惊奇地发现:原串的回文串直径恰好是新串中回文串的半径(上取整)减一。人类智慧

高妙的实现
1
2
3
4
5
6
7
8
9
10
11
12
void manacher(){
int l=1,r=1,L=2*ls+1;//l和r是右端最靠右的一对回文串边界,L是新字符串长
for(int i=2;i<=L;i++){
int k=r<i?1:min(len[l+r-i],r-i+1);
while(1<=i-k&&i+k<=L&&s[i-k]==s[i+k]) k++;
len[i]=k--;
if(i+k>r){
r=i+k;
l=i-k;
}
}
}//求出新串的半径后就可以为所欲为了

SA

可以在 时间复杂度内求出一个字符串的每一个后缀的字典序的排名。用到了基数排序和倍增的思想。每次处理出每一个位置向后延伸一定长度的字符串的排名,借助上一次的处理结果可以双关键字排序,从而不断进行更新。

算法流程

准备四个数组:,其中 是第一关键字的值, 是第二关键字的排序,即 代表第二关键字第 小的下标。 是统计某一个值的出现次数(用于基数排序), 用来暂时存储当前答案,即当前排名为 的后缀下标。

初始化:

  • 统计最开始的 。直接按照字符大小赋值即可。顺便统计
  • 数组求前缀和。
  • 统计最开始的 。简单来说就是sa[c[x[i]]--]=i

对于每一次更新,记当前后缀长度为

  • 首先根据上一次的 处理出 数组。
    • 对于后 位,由于第 位不存在,第二关键字为空,可以视为最小,直接存在 中。
    • 对于其他位 ,它们的第二关键字应该对应于上一次的 数组中的 的位置,即如果 ,那么 将成为 的第二关键字。
    • 按照 顺序遍历,则先遍历到的第二关键字一定更小;记一个下标 不断向 中添加新元素即可。
  • 然后更新 。首先清空 ,然后借助 数组更新 ,再对 求前缀和,如刚才方法更新
  • 最后更新 数组。暂时使用闲置的 数组存储之前的 。根据刚刚处理出的 ,顺序遍历 数组,初始设置num=1,如果发现第一关键字或第二关键字有变动,就执行++num。当 时,意味着所有后缀的第一关键字值都不相同,此时就可以跳出循环了。
优秀的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void SA(){
for(int i=1;i<=n;i++) x[i]=s[i],c[x[i]]++;
for(int i=2;i<=m;i++) c[i]+=c[i-1];//m是当前x中不同值个数,初始为字符集大小
for(int i=n;i>=1;i--) sa[c[x[i]]--]=i;
for(int k=1;k<=n;k<<=1){
int num=0;
for(int i=n-k+1;i<=n;i++) y[++num]=i;
for(int i=1;i<=n;i++) if(sa[i]-k>0) y[++num]=sa[i]-k;
for(int i=1;i<=m;i++) c[i]=0;
for(int i=1;i<=n;i++) c[x[i]]++;
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i];//原本下标是1~n,现在是y1~yn
swap(x,y);//y没用了,暂存一下之前的x
num=1;
x[sa[1]]=1;
for(int i=2;i<=n;i++){
if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]) x[sa[i]]=num;
else x[sa[i]]=++num;
}
if(num==n) break;
m=num;//更新字符集大小
}
}