前言
学习了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]<rank[j]
)
当然i=j的话LCP是后缀i的长度有些时候这个点可能会被坑到。
例题:SDOI2016生成魔咒
求第k小子串(两个子串不同当且仅当它们本质不同)
顺序扫,每次增加的子串个数是n-i+1-height[i]的。
扩展开来说,就是要知道增加一个后缀会增加的本质不同的子串个数,结合LCP就可做了。
例题:GDOI2012字符串
然后再随手扔一道SA的题:JZOJ1598文件修复