后缀数组入门
后缀数组相关定义
sa[i]
:表示按照字典序排序后,第i名后缀开头下标
rk[i]
:表示后缀i的排名
后缀i
:以下标i为开头的后缀
暴力求法
一切都从暴力开始,哈哈哈哈
我们把n个后缀sort一遍,复杂度是O(n2logn)。
倍增优化
使用倍增的思想进行优化。
-
按照每个后缀的前1个字母排序。
-
按照每个后缀的前2个字母排序
-
按照每个后缀的前4个字母排序
...
-
直到每个后缀的排名不一样
最多会排序logn次
具体排序
解释:
首先第一次按照每个后缀的第一个字母排序,每个后缀的排名为rk[i]
。
第二次按照后缀的前两个字母排序,将第i个后缀的排名和第i+1个后缀的排名作为第i个后缀的第一二关键字,如果i+1>n,第二关键字为0,进行排序。
这时rk[i]
表示的是按照前两个字母排序后,后缀i的排名。
那么我们如何按照后缀的前四个值排序呢?
把第i个排名和第i+2的排名作为第i个的一二关键字,然后进行排序。
...
如果这个过程中使用的是sort排序,那么复杂度是O(nlog2n),
基数排序优化
使用基数排序将一次排序的复杂度降为O(n)
代码
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e6+10;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
int pos[N],sa[N],cnt[N],rk[N],oldrk[N];
char str[N];
bool cmp(int x,int y,int k)
{
return oldrk[x]==oldrk[y]&&oldrk[x+k]==oldrk[y+k];
}
void solve()
{
int m=200;
int n=strlen(str+1);
for(int i=1; i<=n; ++i)
++cnt[rk[i]=str[i]];
for(int i=1; i<=m; ++i)
cnt[i]+=cnt[i-1];
for(int i=n; i; --i)
sa[cnt[rk[i]]--]=i;
for(int k=1; k<=n; k<<=1)
{
int num=0;
/*
pos[i]表示第二关键字排名为i的第一关键字的位置
n-k+1~n都没有第二关键字,补0,所以它们的排名在前面
*/
for(int i=n-k+1; i<=n; ++i)
pos[++num]=i;
/*
遍历sa,如果sa[i]>k,说明排名为i的可以作为第二关键字
*/
for(int i=1; i<=n; ++i)
{
if(sa[i]>k)
pos[++num]=sa[i]-k;
}
memset(cnt,0,sizeof(cnt));
for(int i=1; i<=n; ++i)
++cnt[rk[i]];
for(int i=1; i<=m; ++i)
cnt[i]+=cnt[i-1];
/*
第一关键字相同的时候按照第二关键字由大到小进行排序
*/
for(int i=n; i; --i)
sa[cnt[rk[pos[i]]]--]=pos[i];
/*
排序完成之后,现在更新每个后缀的新排名
*/
num=0;
memcpy(oldrk,rk,sizeof(rk));//复制当前排名到oldrk。
/*
判断当前排名和前一排名的第一二关键字是否一样,如果一样num不用++
否则num++
使用cmp减少不连续内存访问,在数据范围较大时效果比较明显。
*/
for(int i=1; i<=n; ++i)
rk[sa[i]]=cmp(sa[i],sa[i-1],k)?num:++num;
if(num==n)//有n个排名,排序完成
break;
m=num;//排名的最大值为num
}
for(int i=1; i<=n; ++i)
rk[sa[i]]=i;
}
int main()
{
scanf("%s",str+1);
solve();
int len=strlen(str+1);
for(int i=1;i<=len;i++)
printf("%d ",sa[i]);
printf("\n");
return 0;
}
height数组
lcp(i,j)
:表示后缀i和后缀j的最长公共前缀长度
height[i]=lcp(sa[i],sa[i-1])
//第i名和第i-1名的最长公共前缀长度
height[1]=0
求height数组引理:
height[rk[i]]>=height[rk[i-1]]-1
证明:
引自OI-WiKi。
O(n)求height数组代码:
利用引理暴力:
for (i = 1, k = 0; i <= n; ++i)
{
if (k) --k;//>=height[rk[i-1]]-1,所以减1
while (s[i + k] == s[sa[rk[i] - 1] + k]) ++k;
ht[rk[i]] = k;
}
height数组简单应用
\(lcp(sa[i],sa[j])=min(height[i+1]...height[j])\)
\(lcp(i,j)=min(height[rk[i]+1]...height[rk[j]])\)
小声哔哔
当多组输入的时候,最好让str[n+1]等于一个没有出现过的字符,虽然没有出过错。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步