【学习笔记】SA/SAM
1.【SA】
参看oi-wiki
1.2【数组】
-
\(sa_i\):第 \(i\) 小的字符串的下标
-
\(rk_i\):下标为 \(i\) 的字符串的排名;是求 \(sa\) 的中间产物,并无应用
-
\(h_i\):下标为 \(i\) 的字符串与 \(i-1\) 的字符串的最长公共前缀长度
-
\(height\):排名为 \(i\) 的字符串与 \(i-1\) 的字符串最长公共前缀长度;是 SA 的精髓,也是相比于 SAM 的优势所在
1.3【基数排序正确性】
基数排序是稳定排序,故而正确
1.4【简单应用】
本质不同子串计数:减去 \(sum(height[i])\)
1.5【构建方式】
1.5.1【基础构建】
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
char c[N];
int sa[N],rk[N*2];
int tmp[N];
int tot;
int up=131;
int n;
int bin[N];
void sortsa(){//按照第一关键字排序
memset(bin,0,sizeof bin);
for(int i=1;i<=n;i++) bin[rk[i]]++;
for(int i=1;i<=up;i++) bin[i]+=bin[i-1];
for(int i=n;i>0;i--) sa[bin[rk[tmp[i]]]--]=tmp[i];//保证稳定排序
}
int main(){
string s;cin>>s;
n=0;
for(auto v:s) c[++n]=v;
for(int i=1;i<=n;i++){
rk[i]=c[i];
tmp[i]=i;
}
sortsa();
//rk是离散化后的关键字
for(int i=1;i<n;i<<=1){
tot=0;
//tmp作sa(第二关键字排序)
for(int j=n-i+1;j<=n;j++) tmp[++tot]=j;
for(int j=1;j<=n;j++){
if(sa[j]-i>0) tmp[++tot]=sa[j]-i;
}
sortsa();
//以上可直接替换为sort
//tmp作rk(重新分配)
tmp[sa[1]]=up=1;
for(int j=2;j<=n;j++){
if(rk[sa[j]]!=rk[sa[j-1]]||rk[sa[j]+i]^rk[sa[j-1]+i]) up++;
tmp[sa[j]]=up;
}
memcpy(rk,tmp,sizeof tmp);
}
for(int i=1;i<=n;i++) cout<<sa[i]<<" ";
return 0;
}
1.5.2【height构建】
\(h\) 递推性质: \(h[i]≥h[i−1]−1\)
\(height\) 区间最小值定理:任意两个后缀的 \(LCP\) 等于它们排名区间内 \(height\) 数组的最小值
int height[N];
void getHeight(){
int k=0;
for(int i=1;i<=n;i++){
if(rk[i]==1){
k=height[rk[i]]=0;
continue;
}
if(k) k--;
int j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&c[i+k]==c[j+k]) k++;
height[rk[i]]=k;
}
}
1.6【优势】
SAM功能比SA丰富许多,但是SA仍有一些优势:
SA相比SAM最核心的不可替代优势是 \(LCP\)(最长公共前缀)查询。
配合 \(height\) 数组,SA能 \(O(1)\) 回答任意后缀的 \(LCP\),而SAM需要求 \(Parent\) 树的 \(LCA\),实现繁琐得多。
此外,SA还有两大绝对优势:
无视字符集:处理大字符集时内存稳定,SAM则可能内存爆炸。
天生有序:直接输出所有后缀的字典序排名,SAM做不到这一点。
而SA掌握以上内容即可,其余建议使用SAM
2.【SAM】
请移步Link

浙公网安备 33010602011771号