[sa][后缀数组]关于后缀数组的若干技巧

来源:互联网 发布:软件项目进展汇报 编辑:程序博客网 时间:2024/05/11 18:25

简单入门

sa[i]表示将后缀从小到大排名后,第i小的后缀的位置
rank[i]表示i这个后缀的排名
height[i]表示sa[i]和sa[i-1]的lcp
lcp(i,j)表示sa[i]和sa[j]的lcp。则lcp(i,j)=min(height[i+1],……,height[j]).

求后缀数组的原理:
将每次我们求的后缀的长度*2,求这个长度的后缀的排名。则求这次的后缀的排名时可以使用以前的信息。如求位置为i,长度为j*2的后缀的排名,则可以通过比较位置i,长度为j的后缀(第一关键字)和位置i+j,长度为j的后缀(第二关键字)的排名(上一次长度的信息)来得到i位置长度为j的后缀的排名。

求后缀数组的方法:
那么如果求每个后缀时都直接保存上次的信息,然后快排,则效率为nlog^2n.使用基数排序的技巧来优化,使得效率达到nlogn。
记w[i]表示第一关键字排名为i的后缀数量,然后做一遍前缀和的值。即所有第一关键字为i时第二关键字最大时的后缀的排名。
x[i]表示上一轮排名后,i的排名。
暂时的rank[i]表示第二关键字排名第i小的第一关键字的位置。
则我们将第二关键字倒序枚举,即从大到小枚举第二关键字,对于第一关键字相等的后缀,第二关键字大的分配到的排名应该越大,即sa[w[rank[i]]–]=rank[i]。
给出样例:
对于基排我们有
10,11,12,13,先将十位个位分开丢进桶
1:0 1 2 3
则13分配到的排名应该尽量大,即sa[x]=(13的位置)和sa[y]=(12的位置),x>y。
后缀数组中的实现为,先枚举到第二关键字大的3,然后rank[]得到其第一关键字为1,w表示1这个桶中最大的那个数被取出时的排名,实现了13的排名取值。

给出模板:

inline void Sa(){    int m=127,u,v;    for(int i=1;i<=m;++i) w[i]=0;    for(int i=1;i<=n;++i) w[x[i]=sr[i]]++;    for(int i=1;i<=m;++i) w[i]+=w[i-1];    for(int i=n;i>=1;--i) sa[w[x[i]]--]=i;    for(int j=1;j<=n;j*=2)    {        int cnt=0;        for(int i=n-j+1;i<=n;++i) rank[++cnt]=i;        for(int i=1;i<=n;++i)        if(sa[i]>j) rank[++cnt]=sa[i]-j;        for(int i=1;i<=m;++i) w[i]=0;        for(int i=1;i<=n;++i) w[x[i]]++;        for(int i=1;i<=m;++i) w[i]+=w[i-1];        for(int i=n;i>=1;--i) sa[w[x[rank[i]]]--]=rank[i];        m=0;        for(int i=1;i<=n;++i)        {            u=sa[i];v=sa[i-1];            if(x[u]!=x[v]||x[u+j]!=x[v+j]) ++m;            rank[u]=m;        }        if(m==n) break;        for(int i=1;i<=n;++i) x[i]=rank[i];    }    int j=0;    for(int i=1;i<=n;++i)    {        v=sa[rank[i]-1];        j=max(0,j-1);        while(sr[i+j]==sr[v+j]) ++j;        hei[rank[i]]=j;    }}

若干技巧(持续更新,有待发现

很多题不好做就先二分再想想怎么check

本质不同的子串个数为:
insa[i]hei[i],显然对于sa[i]这个位置,有hei[i]个相同的前缀,则之后贡献的是不同的

原创粉丝点击