(color{#0066ff}{题目描述})
读入一个长度为 n的由大小写英文字母或数字组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置。位置编号为 1 到 n。
(color{#0066ff}{输入格式})
一行一个长度为 n 的仅包含大小写英文字母或数字的字符串。
(color{#0066ff}{输出格式})
一行,共n个整数,表示答案。
(color{#0066ff}{输入样例})
ababa
(color{#0066ff}{输出样例})
5 3 1 4 2
(color{#0066ff}{数据范围与提示})
(nleq 10^6)
(color{#0066ff}{题解})
简单来说,就是给你一个字符串,让你对他的n个后缀按字典序进行排序
给出一些定义
sa[i] 代表排名为i的后缀的第一个字母在原串中出现的位置
rk[i] 代表从i位置开始的后缀的排名
可以发现,上面两个数组互逆
x[i] 代表后缀i的第一关键字的排名
y[i] 代表第二关键字排名为i的,在以第一关键字排序的排名
c[i] 为基数排序用的桶
正常求后缀排名是(O(n^2))的
我们通过倍增来将其优化为nlogn
举个例子 abdae
后缀分别为
e
ae
dae
bdae
abdae
将上述后缀按顺序称为1--5号
假设通过一轮基数排序成功比较出了第一个字母(O(n))
下面就要比较第二个字母
可以发现,每个后缀的第二个字母,它下一个后缀的第一个字母,而第一个字母我们已经求出来
求出了一半,另一半也出来了
那么鸡排的1,2,3,4,5,6可以变成1,2,4,8,16,32,成了个log
#include <bits/stdc++.h>
const int maxn = 1e6 + 10;
char s[maxn];
int x[maxn], y[maxn], sa[maxn], c[512], rk[maxn];
int n, m;
void SA() {
//第一遍鸡排,以字母作关键字
for(int i = 1; i <= n; i++) c[x[i] = s[i]]++;
for(int i = 2; i <= m; i++) c[i] += c[i - 1];
//倒着分配排名
for(int i = n; i >= 1; i--) sa[c[x[i]]--] = i;
//倍增
for(int k = 1; k <= n; k <<= 1) {
int num = 0;
for(int i = n - k + 1; i <= n; i++) y[++num] = i;
//y[i] 代表第二关键字排名为i的,在以第一关键字排序的排名
//不难发现,从n-k+1到n这些位置的后缀是没有第二关键字的
//所以字典序小,放进y里
for(int i = 1; i <= n; i++) if(sa[i] > k) y[++num] = sa[i] - k;
//排名为i的数 在数组中是否在第k位以后
//如果满足(sa[i]>k) 那么它可以作为别人的第二关键字,就把它的第一关键字的位置添加进y就行了
for(int i = 1; i <= m; i++) c[i] = 0;
//上次循环算出了本次的第一关键字
for(int i = 1; i <= n; i++) c[x[i]]++;
for(int i = 2; i <= m; i++) c[i] += c[i - 1];
//y是在第一关键字的排名,套上个x就是以第二关键字排序排名,再套上个c,就是分配排名
//因为y的顺序是按照第二关键字的顺序来排的
//第二关键字靠后的,在同一个第一关键字桶中排名越靠后
for(int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i], y[i] = 0;
std::swap(x, y);
//此时x是0了,因为生成新的x需要旧的,就是一个临时代替作用
//因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字
//重新排名
x[sa[1]] = 1, num = 1;
for(int i = 2; i <= n; i++)
x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k])? num : ++num;
//如果当前已经比出所有后缀,结束就行了
if(num == n) break;
//下次用的不是ascii了,用的是排名,所以改变m
m = num;
}
for(int i = 1; i <= n; i++) printf("%d%c", sa[i], i == n? '
' : ' ');
}
int main() {
scanf("%s", s + 1);
//n是字符串长度,m是关键字范围
//刚开始字符为关键字,122是'z'的ascii码
//之后以排名作为关键字,会改变
n = strlen(s + 1), m = 122;
SA();
return 0;
}