学习小记——后缀数组

定义

在字符串处理当中,后缀树和后缀数组都是非常有力的工具,其中后缀树大家了解得比较多,关于后缀数组则很少见于国内的资料。其实后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也不太逊色,并且,它比后缀树所占用的空间小很多。可以说,在信息学竞赛中后缀数组比后缀树要更为实用。

变量定义

Suffix(i):表示S[i..len(S)],即从第i位开始的字符串后缀。

SA数组:后缀数组SA是一个一维数组,它保存1..n的某个排列SA[1],SA[2],……,SA[n],并保证Suffix(SA[i])<Suffix(SA[i+1]),1≤i<n。也就是将S的n个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入SA中。

rank数组:名次数组 Rank[i] 保存的是 Suffix(i) 在所有后缀中从小到大排列的“ 名次 ”后缀数组SA与名次数组Rank的对应关系。简单的说,后缀数组(SA)是“ 排第几的是谁?”,名次数组(RANK)是“ 你排第几?”。容易看出,后缀数组和名次数组为互逆运算。

height数组:height[i]=LCP(i-1,i),即第rank[i-1]与rank[i]的最长前缀是多长。

2倍增算法

比较容易实现的一种(相对于DC3算法,虽然DC3比较快,但是实现有点复杂,我不会……),一般的题目应该也够用了,时间复杂度O(n*log n),还算比较快了。

思路

我们倍增一个k,与RMQ的思想差不多,以 2k1 的结果来推出 2k ,一步步倍增就能够实现求出所有的后缀的排名(具体定义详见《后缀数组——处理字符串的有力工具(罗穗骞)》)。(借了个图)
这里写图片描述
假设现在到了第三轮,k为2,第一关键字是上一轮同一位置的rank,第二关键字为上一轮向后k个位置的rank,然后进行双关键字排序(这里推荐用基数排序,当然快排也行,但是慢一点)。

memset(t,0,sizeof(t));
fo(i,1,n) t[rank[i+k]]++;
fo(i,1,da) t[i]+=t[i-1];
for(i=n;i>0;i--) se[t[rank[i+k]]--]=i;
//第二关键字排序   
memset(t,0,sizeof(t));
fo(i,1,n) t[rank[i]]++;
fo(i,1,da) t[i]+=t[i-1];
for(i=n;i>0;i--) sa[t[rank[se[i]]]--]=se[i];
//第一关键字排序

就这样可以求出一个SA数组,但是数组之中都是不重复的,我们还要处理一下相同的情况。当S[i]=S[j]&&S[i+k]=S[j+k]时就定义为相同。

bool cmp(int *a,int x,int y,int z){return a[x]==a[y]&&a[x+z]==a[y+z];}

j=1;zs[sa[1]]=1;
        fo(i,2,n) 
            zs[sa[i]]=cmp(rank,sa[i],sa[i-1],k)?j:++j;

求完了rank和SA数组后,就要求height数组,通过这个数组的一个特殊性质:h[i]≥h[i-1]-1,我们可以把复杂度降至 O(n)

i=j=k=0;
    for(int i=1;i<=n;h[rank[i++]]=k)
        for(k?k--:0,j=sa[rank[i]-1];a[i+k]==a[j+k];++k);

模板题

文件修复(JZOJ 1598)

//快速排序
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=1e5+5;
struct arr{
    int x,y,bh;
}t[N];
char s[N];
int n,i,j,k,rank[N],sa[N],ws[N],a[N],h[N],num,ans;
int cmp(arr x,arr y){return x.x<y.x||(x.x==y.x&&x.y<y.y);}
void deal(){
    fo(i,1,n) rank[i]=a[i];
    for(k=0;k<=n;){
        fo(i,1,n){
            t[i].x=rank[i],t[i].bh=i;
            if(i+k>n) t[i].y=0;else t[i].y=rank[i+k];
        }
        sort(t+1,t+n+1,cmp);
        rank[t[1].bh]=1;sa[1]=t[1].bh;num=1;
        fo(i,2,n){
            if(t[i-1].x!=t[i].x||t[i-1].y!=t[i].y) num++;
            rank[t[i].bh]=num;
            sa[i]=t[i].bh;
        }
        k*=2;
        if(k==0) k=1;
    }
    i=j=k=0;
    for(int i=1;i<=n;h[rank[i++]]=k)
        for(k?k--:0,j=sa[rank[i]-1];a[i+k]==a[j+k];++k);
}
int main(){
    scanf("%s",s);
    n=strlen(s);
    fo(i,1,n) a[i]=s[i-1];
    deal();
    ans=h[2];
    fo(i,3,n) ans+=max(0,h[i]-h[i-1]);
    printf("%d",ans);
}

还有一种快一点

//基数排序
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=1e5+5;
char s[N];
int n,i,j,k,rank[N],sa[N],zs[N],se[N],t[N],a[N],h[N],num,ans,da;
bool cmp(int *a,int x,int y,int z){return a[x]==a[y]&&a[x+z]==a[y+z];}
void deal(){
    fo(i,1,n) rank[i]=a[i],se[i]=i;
    for(k=0;k<=n;){
        memset(t,0,sizeof(t));
        fo(i,1,n) t[rank[i+k]]++;
        fo(i,1,da) t[i]+=t[i-1];
        for(i=n;i>0;i--) se[t[rank[i+k]]--]=i;

        memset(t,0,sizeof(t));
        fo(i,1,n) t[rank[i]]++;
        fo(i,1,da) t[i]+=t[i-1];
        for(i=n;i>0;i--) sa[t[rank[se[i]]]--]=se[i];
        j=1;zs[sa[1]]=1;
        fo(i,2,n) 
            zs[sa[i]]=cmp(rank,sa[i],sa[i-1],k)?j:++j;
        swap(zs,rank);
        if(k==0) k=1;else k*=2;
    }
    i=j=k=0;
    for(int i=1;i<=n;h[rank[i++]]=k)
        for(k?k--:0,j=sa[rank[i]-1];a[i+k]==a[j+k];++k);
}
int main(){
    scanf("%s",s);
    n=strlen(s);
    fo(i,1,n) a[i]=s[i-1],da=max(da,a[i]);
    da=max(n,1000);
    deal();
    ans=h[2];
    fo(i,3,n) ans+=max(0,h[i]-h[i-1]);
    printf("%d",ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值