前言
SA是一种解决多模板匹配问题的算法。大致就是将后缀处理出来然后按照字典序排个序。时间主要浪费在排序上。
sa数组sa[i]表示rk为i的后缀的开始位置。
rk数组rk[i]表示以i位置开始的后缀的rank为多少。
基数排序
先排个位,然后十位依次往下,稳定算法。
模板
const int N = 1e6 + 5; // 开二倍吧
int sa[N], rk[N], Height[N], tax[N], tp[N], a[N], n, m;
//rk[i] 第i个后缀的排名; SA[i] 排名为i的后缀位置; Height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP
//tax[i] 计数排序辅助数组; tp[i] rk的辅助数组(计数排序中的第二关键字),与SA意义一样。
//a为原串
void Rsort() { //基数排序 要弄懂
//rk第一关键字,tp第二关键字。
for(int i = 0; i <= m; i++) tax[i] = 0;
for(int i = 1; i <= n; i++) tax[rk[tp[i]]]++;
for(int i = 1; i <= m; i++) tax[i] += tax[i - 1];
for(int i = n; i >= 1; i--) sa[tax[rk[tp[i]]]--] = tp[i];//确保满足第一关键字的同时,再满足第二关键字的要求
}
int cmp(int *f, int x, int y, int w) { return f[x] == f[y] && f[x + w] == f[y + w]; }
//通过二元组两个下标的比较,确定两个子串是否相同
void init() {
memset(tp, 0, sizeof(tp));
memset(rk, 0, sizeof(rk));
memset(a, 0, sizeof(a));
}
void Suffix() {
for(int i = 1; i <= n; i++) rk[i] = a[i], tp[i] = i;
m = 128; Rsort();
for(int w = 1, p = 1, i; p < n; w += w, m = p) {
//w 当前一个子串的长度; m 当前离散后的排名种类数
//当前的tp(第二关键字)可直接由上一次的SA的得到
for(p = 0, i = n - w + 1; i <= n; i++) tp[++p] = i;
for(int i = 1; i <= n; i++) if(sa[i] > w) tp[++p] = sa[i] - w;
//更新SA值,并用tp暂时存下上一轮的rank(用于cmp比较)
Rsort(); swap(rk, tp), rk[sa[1]] = p = 1;
//用已经完成的SA来更新与它互逆的rank,并离散rank
for(int i = 2; i <= n; i++) rk[sa[i]] = cmp(tp, sa[i], sa[i - 1], w) ? p : ++p;
}
}
void cal_height() { //这个知道原理后就比较好理解程序
int j, k = 0;
for(int i = 1; i <= n; Height[rk[i++]] = k)
for(k = k ? k-1 : k, j = sa[rk[i] - 1]; a[i + k] == a[j + k]; ++k);
}
int dp[N][20];
void ST() {
for(int i = 1; i <= n; i++) dp[i][0] = Height[i];
for(int j = 1; j <= 20; j++)
for(int i = 1; i + (1 << j) - 1 <= n; i++)
dp[i][j] = min(dp[i][j - 1], dp[i + (1 <<(j - 1))][j - 1]);
}
int RMQ(int l, int r) {
int k = log2(r - l + 1);
return min(dp[l][k], dp[r - (1 << k) + 1][k]);
}