P3809 【模板】后缀排序

(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;
}
原文地址:https://www.cnblogs.com/olinr/p/10158778.html