芝士:后缀数组(SA)
例子
基数排序
它的工作原理是将待排序的元素拆分为k个关键字(比较两个元素时,先比较第一关键字,如果相同再比较第二关键字……),然后先对第k关键字进行稳定排序,再对第k-1关键字进行稳定排序,再对第k-2关键字进行稳定排序……最后对第一关键字进行稳定排序,这样就完成了对整个待排序序列的稳定排序。
--OI WIKI
过程
pace 1
考虑对两个字符串进行比较
定义其为\(s1,s2\)
如果\([0,i]\)已经能比较出大小,那么就不需要考虑\([i+1,lens)\)的关系了
反之,如果\([0,i]\)不能比较出大小,那么就需要考虑\([i+1,lens)\)的关系
pace 2
定义位置\(i\)的字符串为\([i,lens)\)的字符
比如\(abaaba\)
考虑对于所有位置\(i\)字符串按第一位进行排序
那么排完序就会有
现在单独考虑第二位的大小,考虑到后缀之间的联系,现在真的需要知道每一个后缀具体是什么样子么?
位置\(0\)的字符串的第二位不就是位置\(1\)的字符串的第一位?
那么对于每一个位置都可以找到一个第一位与之第二位相对应
那么我们就可以知道只考虑第二位的后缀之间的大小关系
然后我们知道第二位的大小关系,又知道第一位的大小关系、
那么运用基数排序的思想,就可以知道前两位,即\([0,1]\)的大小关系
pace 3
按照pace2的内容进行递推,
如果我们知道只考虑前k位的大小关系,即\([0,k-1]\)的大小关系
现在我们想求出只考虑\([0,2k-1]\)的大小关系
考虑对于位置\(i\)的字符串,其\([k,2k-1]\)的字符就是位置\(i+k\)的前\(k\)位
故可以知道只考虑\([k,2k-1]\)的大小关系
那么按照基数排序,就可以有\([0,2k-1]\)的大小关系
每一次基数排序的时间复杂度为\(O(n)\)
一共要倍增\(log_n\)次
所以总的时间复杂度为\(O(nlog_n)\)
代码
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
char s[1000005];
int lens,sa[1000005],m;//排名为i的位置&关键字的种类
int rk[1000005],tp[1000005];//位置i的排名&第二关键字排名为i的位置
int t[1000005],temp[1000005];//桶&临时数组
void ssort()//基数排序
{
for(int i=1;i<=m;i++)
t[i]=0;
for(int i=1;i<=lens;i++)
t[rk[i]]++;
for(int i=2;i<=m;i++)
t[i]+=t[i-1];
for(int i=lens;i>=1;i--)
sa[t[rk[tp[i]]]--]=tp[i];
}
void getsa()
{
for(int i=1;i<=lens;i++)
{
rk[i]=s[i];
tp[i]=i;
}
ssort();
for(int k=1;k<=lens;k<<=1)
{
int cnt=0;
for(int i=lens-k+1;i<=lens;i++)
tp[++cnt]=i;
for(int i=1;i<=lens;i++)
if(sa[i]>k)
tp[++cnt]=sa[i]-k;
ssort();
swap(rk,tp);
rk[sa[1]]=1;
cnt=1;
for(int i=2;i<=lens;i++)
if(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])
rk[sa[i]]=cnt;
else
rk[sa[i]]=++cnt;
m=cnt;
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>(s+1);
lens=strlen(s+1);
m='z';
getsa();
for(int i=1;i<=lens;i++)
cout<<sa[i]<<' ';
return 0;
}
后缀的LCT
咕咕咕

浙公网安备 33010602011771号