[MOBAN]后缀数组
wc早上讲了很骚气的字符串算法和骚气的具体数学,睡眠很香,发现不会课程的前置知识后缀数组ora,学一波
参考博客:
https://www.cnblogs.com/RabbitHu/p/UOJ35.html
https://www.cnblogs.com/victorique/p/8480093.html
https://www.cnblogs.com/zwfymqz/p/8413523.html
SA[i] 排名i的后缀的位置
RANK[i] suf[i]的排名
height[i] (关键)表示后缀sa[i]和后缀sa[i-1]的lcp(最长公共前缀),并且满足height[rk[i]]>= height[rk[i-1]]-1
既是位置上i的height >= i-1的height-1,这样我们可以线性求height
LCP(i,j) = min: heghit[k] rank[i] < rank[k] <= rank[j]
可重叠最长重复子串 max:height
本质不同子串:LEN-sa[i]+1-height[i]
#include<cstring>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
const int maxn = 1e5+5;
char s[maxn];
int buf1[maxn],buf2[maxn],ton[maxn];
int n,sa[maxn],rnk[maxn],height[maxn];
void suffixsort() {
int *x = buf1 , *y = buf2; int m = 127;
//x,y临时数组,m基数排序最大值
fill(ton,ton+m+1,0);//清空桶
for(int i=1;i<=n;i++) ton[ x[i] = s[i] ]++;
for(int i=1;i<=m;i++) ton[i] += ton[i-1];
for(int i=n;i>=1;i--) sa[ton[x[i]]--] = i;
for(int k=1;k<=n;k<<=1) { //枚举比较长度的1/2
int p = 0; //新桶字符串数
for(int i=n-k+1;i<=n;i++) y[++p] = i;
for(int i=1;i<=n;i++) {
if(sa[i]>k) y[++p] = sa[i] - k;//加入第一关键字
}
//为第二关键字排序,保证在第一关键字相同的情况下第二关键字顺序
fill(ton,ton+m+1,0);
for(int i=1;i<=n;i++) ton[x[y[i]]]++;
for(int i=1;i<=m;i++) ton[i] += ton[i-1];
for(int i=n;i>=1;i--) sa[ton[x[y[i]]]--] = y[i];
//为第一关键字排序
swap(x,y);//y上一轮的字符构造出x现在新的一轮的值
p = x[sa[1]] = 1;
for(int i=2;i<=n;i++) {
if(y[sa[i]]==y[sa[i-1]]&&y[sa[i-1]+k]==y[sa[i]+k]) x[sa[i]] = p;
else x[sa[i]] = ++p;
}
m = p;
if(m==n) break;
}
for(int i=1;i<=n;i++) rnk[sa[i]] = i;
//sa保存排名i在哪儿,rnk保存i的排名
for(int i=1,CD=0;i<=n;i++) {
if(rnk[i]==1) continue;
if(CD>0) CD--;
int lst = sa[rnk[i]-1];
while(s[i+CD]==s[lst+CD]&&i+CD<=n&&lst+CD<=n) CD++;
height[rnk[i]] = CD;
}
//height保存rank i 和 rank i-1之间的LCP
//那么对于LCP(i,j) = min:height[k] k (rnk[i]<k<=rnk[j]]
}
int main() {
scanf("%s",&s[1]);
n = strlen(s+1);
suffixsort();
for(int i=1;i<=n;i++) {
printf("%d ",sa[i]);
}
puts("");
for(int i=2;i<=n;i++) {
printf("%d ",height[i]);
}
puts("");
}