后缀数组复习小记

前言

学习了SAM为什么要学习SA?(没复习之前我就是传说中的只会SAM不会SA的蒟蒻)
因为有些问题SA可做SAM不可做。
(以下开始扯淡,大家可以跳过前言部分了……
我还记得我上一次打SA是将近一年前的GDOI赛场上——
day1只考了50分的我对比同年级其他神犇十分失落。day2我开始看题:
t1sb广搜呀!
t2sbtarjan呀!
t3奥妙重重,字符串的题……恩,我只会sa,想想如何搞一波……
于是得到了一个线性算法!(除去getsa的复杂度,而理论上getsa也可以用DC3做到线性,所以我们认为此算法是线性算法)
考场上年轻的我“证明”了它的正确性,并且出于好的策略打了对拍,没有报错!
t4不会,暴力。
自信估分320。
评测结果:
t1被卡时,60分。
t2出现莫名错误60分(然而后来相同的代码我在OJ上直接过了……当时复评的时候也说这题有问题暂时不进行复评)
t4文件名打错……
t3——
AC!
AC!
AC!
这题成了三天我唯一AC的题目……
不过后来我发现我无法严谨证明我的算法是正确的,但是很弱很弱的我也没找到反例,而随机数据下压根卡不掉……
时隔快一年,这一年中,我居然再也没有打过SA……
是的,那道题居然是我人生中最后一次打SA!
GDOI快到了,我要复习SA!
后续故事:在我正在敲这篇的时候,howarli找到我GDOI赛场上打的算法的反例QAQ

算法思路

好简单的倍增算法咧……
先得到初始rank,此时桶的范围与实际题目需求要关。
然后开始倍增,每次把二元组进行桶排序,然后更新rank。
最后由rank推出sa。
小优化:当rank数组是n的一个排列时已经可以退出倍增过程。
实现要点记忆:
任何时候我们都把元素倒着插入到序列中(减少思维难度,加快码速,而且这样一定不会挂)
桶排前清空桶。
初始rank桶通常大小为256(字符),但有些题目字符实际是数字,根据实际选择桶大小。
而倍增中rank的范围是[1,n]所以桶大小一定为n。
将rank数组的范围开到两倍就不要处理二元组第二个元素是溢出去的情况。
对二元组进行桶排序,先排第二维再排第一维,接着先用一个酱油数组存储新的rank,再赋值给rank。因为得到新rank需要旧rank进行比较,直接修改旧rank会鬼畜哦!
getsa代码附

void getsa(){ fo(i,1,n) b[s[i]]++; fo(i,1,256) b[i]+=b[i-1]; fd(i,n,1) c[b[s[i]]--]=i; t=0; fo(i,1,n){ if (s[c[i]]!=s[c[i-1]]) t++; rank[c[i]]=t; } j=1; while (j<=n){ fo(i,0,n) b[i]=0; fo(i,1,n) b[rank[i+j]]++; fo(i,1,n) b[i]+=b[i-1]; fd(i,n,1) c[b[rank[i+j]]--]=i; fo(i,0,n) b[i]=0; fo(i,1,n) b[rank[i]]++; fo(i,1,n) b[i]+=b[i-1]; fd(i,n,1) d[b[rank[c[i]]]--]=c[i]; t=0; fo(i,1,n){ if (rank[d[i]]!=rank[d[i-1]]||rank[d[i]]==rank[d[i-1]]&&rank[d[i]+j]!=rank[d[i-1]+j]) t++;//这里是比较二元组,可能会有时候脑子模糊比较s了,想清楚! c[d[i]]=t; } fo(i,1,n) rank[i]=c[i]; if (t==n) break;//这里不要手抖打成return啦! j*=2; } fo(i,1,n) sa[rank[i]]=i; }

接下来就是getheight了。
记住枚举的i是对于串S而言的,而height数组的意义是对于排序后的后缀的相邻两位。
getheight代码附

void getheight(){
    k=0;//不清空k小心鬼畜!
    fo(i,1,n){
        if (k) k--;//注意大于0才减
        j=sa[rank[i]-1];//找到前一个后缀
        while (i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;//前面的防溢出判断不能少
        height[rank[i]]=k;//height数组的定义,不要写成height[i]=k
    }
}

经典功能

求两个后缀的LCP(最长公共后缀)
把height数组用rmq维护。
后缀i和后缀j的LCP等价于[rank[i]+1,rank[j]]的height最小值(rank[i]

你可能感兴趣的:(后缀数组复习小记)