前言
五分钟应该学完的算法由于看错无数遍变量意义导致(......)
简介
利用后缀性质倍增合并实现"快速"排序
做法
考虑暴力:把每个后缀提出来然后快排,时间复杂度(O(n^2logn))
从少到多考虑优化:
(h~~~~a~~~~v~~~~a~~~~n~~~~a~~~~a~~~~a)
(2~~~~1~~~~4~~~~1~~~~3~~~~1~~~~1~~~~1)
(21,14,41,13,31,11,11,10)
(6~~~~5~~~~8~~~~4~~~~7~~~~2~~~~2~~~~1)
如果你对(st)表还熟悉的话能马上反应过来我们在干什么
其实就是先得出以(s_i)开头的长度为(1)的排名,再用(s_{i-1})与(s_i)的排名合并,得到所有长度为(2)的排名
通过(logn)次倍增合并,最终得到所有后缀的排名
My complete code
还是来稍微解释一下变量意义吧:(y)第二关键字的排列,(x)第一关键字的排列的映射,(sa)当前伪后缀的排列
在排序(y)的时候,([n-len+1,n])这些是没有第二关键字的(等同于第二关键字最优)
((sa[i]>len)),(sa[i])里存的是位置,当位置(>len)时,是可以影响前半的字符串
#include<bits/stdc++.h>
using namespace std;
typedef int LL;
const LL maxn=1e6+9;
LL n,m;
LL c[maxn],x[maxn],y[maxn],sa[maxn];
char s[maxn];
inline void Get_sa(){
for(LL i=1;i<=n;++i) ++c[x[i]=s[i]];
for(LL i=2;i<=m;++i) c[i]+=c[i-1];
for(LL i=n;i>=1;--i) sa[c[x[i]]--]=i;
for(LL len=1;len<=n;len<<=1){
LL num(0);
for(LL i=n-len+1;i<=n;++i) y[++num]=i;
for(LL i=1;i<=n;++i) if(sa[i]>len) y[++num]=sa[i]-len;
for(LL i=1;i<=m;++i) c[i]=0;
for(LL i=1;i<=n;++i) ++c[x[i]];
for(LL i=2;i<=m;++i) c[i]+=c[i-1];
for(LL i=n;i>=1;--i) sa[c[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
x[sa[1]]=num=1;
for(LL i=2;i<=n;++i)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+len]==y[sa[i-1]+len])?num:++num;
if(num==n) break;
m=num;
}
}
int main(){
scanf(" %s",s+1);
n=strlen(s+1); m=122;
Get_sa();
for(LL i=1;i<=n;++i) printf("%d ",sa[i]);
return 0;
}
常用
(LCP(i,j))为(sa[i])和(sa[j])的最长公共前缀
易证(LCP(i,j)=min{LCP(i,k),LCP(k,j)})
那么我们仅需得到(height[i]=LCP(i,i-1))就能通过倍增求得所有的(LCP)
(h[i]=height[rk[i]]),则(h[i]≥h[i-1]-1)
证明:
(~~~~~)设第(i-1)个字符在(sa)里的前驱为(k)
(~~~~~1.)(k)与(i-1)的首字符不同,则(h[i-1]=0),显然(h[i]≥h[i-1]-1)
(~~~~~2.)(k)与(i-1)的首字符相同,
(~~~~~)则(val=)((k+1)的后缀与(i)的后缀的最长公共前缀)=(height[rk[i-1]]-1),
(~~~~~)显然相邻最优所以(LCP(i,i-1)≥val),所以(h[i]≥h[i-1]-1)
至此我们可以实现(O(n))得到(height[i]),通过(O(nlogn))倍增预处理实现在线(O(logn))查询(LCP(i,j))