后缀数组
引子
遇到了这道题目
双指针迅速切掉以后
来到这道题目
本题和 2007 年 12 月月赛金组同名题目 在题意上一致,唯一的差别是数据范围。
同样一份代码提交后:
100->84
打开题解一看
后缀数组……
进入正题
来自 IOI2009 国家集训队论文 后缀数组 罗穗骞:
后缀数组是处理字符串的有力工具。
定义\(rank\)数组(简称\(rk\)),\(rk[i]\) 为从\(i\)开始的后缀在所有后缀中的排名
定义\(sa\)数组,\(sa[i]\) 为排名为\(i\)的后缀的开头位置
简单的说,后缀数组是“排第几的是谁?”,名次数组是“你排第几?”。
容易看出:\(rk\) 数组和 \(sa\) 数组可以互相推导
\(sa[rk[i]]=i\)
\(rk[sa[i]]=i\)
难点
如何将后缀排序
倍增!
每次对长度为\(2^k\)的字符串进行排序
\(k=0\)时 单个字符按字典序排序
如何排序呢?
基数排序!
设 \(len=2^k\)
经典例子:
\(AABAAAAB\)
当 \(len=1\) 时:
\(A\) \(A\) \(B\) \(A\) \(A\) \(A\) \(A\) \(B\) 按字典序排序
当 \(len=2\) 时
\(AA\) \(AB\) \(BA\) \(AA\) \(AA\) \(AA\) \(AB\) 如何排序呢?
基数排序来啦
我们已经知道前 \(2^{k-1}\) 位组成的字符串和后 \(2^{k-1}\) 位组成的字符串的排名了(当\(len=2^{k-1}\)时算出的排名)
所以直接基数排序(双关键字排序)
具体实现
定义一个结构体
struct node
{
int x,y;//x为第一关键字,y为第二关键字
int id;//记录是第几个字符串
}a[maxn],b[maxn];//两个数组排序
然后,核心代码:
void getrk()
{
for(register int i=1;i<=len;i++)
cnt[s[i]]=1;
for(register int i=1;i<=128;i++)
cnt[i]+=cnt[i-1];
for(register int i=1;i<=len;i++)
rk[i]=cnt[s[i]];
for(register int l=1;l<len;l*=2)
{
for(register int i=1;i<=len;i++)
a[i].x=rk[i],a[i].y=rk[i+l],a[i].id=i;
memset(cnt,0,sizeof(cnt));
for(register int i=1;i<=len;i++)
cnt[a[i].y]++;
for(register int i=1;i<=len;i++)
cnt[i]+=cnt[i-1];
for(register int i=1;i<=len;i++)
b[cnt[a[i].y]--]=a[i];
memset(cnt,0,sizeof(cnt));
for(register int i=1;i<=len;i++)
cnt[a[i].x]++;
for(register int i=1;i<=len;i++)
cnt[i]+=cnt[i-1];
for(register int i=len;i>=1;i--)
a[cnt[b[i].x]--]=b[i];
for(register int i=1;i<=len;i++)
if(a[i].x==a[i-1].x&&a[i].y==a[i-1].y)
rk[a[i].id]=rk[a[i-1].id];
else
rk[a[i].id]=rk[a[i-1].id]+1;
}
for(register int i=1;i<=len;i++)
sa[rk[i]]=i;
}
\(sa\)数组:通过 \(sa[rk[i]]=i\) ,就可以推导出sa数组了
题目链接: 【模板】后缀排序
好题
来一道好题: [USACO07DEC]Best Cow Line G就是上面那道
解法:
将原字符串翻转后接在原字符串上,中间用一个不可能出现的字符代替,求一遍 \(rk\) 数组(为了计算出每个后缀和前缀的顺序),相当于将这个字符串的前缀和后缀的顺序算了一遍,当遇到第一个字符和最后一个字符相同时:看看是前缀的字典序小还是后缀的字典序小就行了
核心代码:
while(st<ed)
{
cnt++;
if(s[st]<s[ed])
{
printf("%c",s[st]);
st++;
}
else if(s[st]>s[ed])
{
printf("%c",s[ed]);
ed--;
}
else
{
if(rk[st]<rk[len-ed+1])
printf("%c",s[st++]);
else
printf("%c",s[ed--]);
}
if(cnt%80==0)
printf("\n");
}
后缀数组应用
典例:快速求取两个后缀间最长公共前缀。
这显然可以二分哈希,但是后缀数组不需要哈希且不会被卡。
咕咕咕

浙公网安备 33010602011771号