P3809 【模板】后缀排序

\(\color{#0066ff}{题目描述}\)

读入一个长度为 n的由大小写英文字母或数字组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置。位置编号为 1 到 n。

\(\color{#0066ff}{输入格式}\)

一行一个长度为 n 的仅包含大小写英文字母或数字的字符串。

\(\color{#0066ff}{输出格式}\)

一行,共n个整数,表示答案。

\(\color{#0066ff}{输入样例}\)

ababa

\(\color{#0066ff}{输出样例}\)

5 3 1 4 2

\(\color{#0066ff}{数据范围与提示}\)

\(n\leq 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? '\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

时间: 2024-10-02 08:55:38

P3809 【模板】后缀排序的相关文章

luogu P3809 【模板】后缀排序

二次联通门 : luogu P3809 [模板]后缀排序 /* luogu P3809 [模板]后缀排序 后缀数组 sa表示 排名为i的是第几个后缀 求出sa数组后输出即可 */ #include <cstdio> #include <cstring> #define Max 1000008 void read (int &now) { register char word = getchar (); for (now = 0; word < '0' || word

P5353 【模板】树上后缀排序

题目地址:[模板]树上后缀排序 我们尝试把普通 SA 改成树上 SA,所以先把普通 SA 贴上来. namespace SA { int sa[N], rk[N], tp[N], tx[N]; inline void tsort() { for (int i = 1; i <= m; i++) tx[i] = 0; for (int i = 1; i <= n; i++) ++tx[rk[i]]; for (int i = 1; i <= m; i++) tx[i] += tx[i-1]

uoj35 后缀排序

题目链接:http://uoj.ac/problem/35 这是一道模板题. 读入一个长度为 n 的由小写英文字母组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置.位置编号为 1 到 n. 除此之外为了进一步证明你确实有给后缀排序的超能力,请另外输出 n?1 个整数分别表示排序后相邻后缀的最长公共前缀的长度. 输入格式 一行一个长度为 n 的仅包含小写英文字母的字符串. 输出格式 第一行 n 个整数,第 i 个整数表示排名为 i 的后缀

uoj #35. 后缀排序

这是一道模板题. 读入一个长度为 n n 的由小写英文字母组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置.位置编号为 1 1 到 n n. 除此之外为了进一步证明你确实有给后缀排序的超能力,请另外输出 n?1 n?1 个整数分别表示排序后相邻后缀的最长公共前缀的长度. 输入格式 一行一个长度为 n n 的仅包含小写英文字母的字符串. 输出格式 第一行 n n 个整数,第 i i 个整数表示排名为 i i 的后缀的第一个字符在原串中的位

1500 后缀排序

1500 后缀排序 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 大师 Master 题解 查看运行结果 题目描述 Description 天凯是MIT的新生.Prof. HandsomeG给了他一个长度为n的由小写字母构成的字符串,要求他把该字符串的n个后缀(suffix)从小到大排序. 何谓后缀?假设字符串是S=S1S2……Sn,定义Ti=SiSi+1……Sn.T1, T2, …, Tn就叫做S的n个后缀. 关于字符串大小的比较定义如下(比较规则和PASCAL中的定义完全

codevs1500 后缀排序

题目描述 Description 天凯是MIT的新生.Prof. HandsomeG给了他一个长度为n的由小写字母构成的字符串,要求他把该字符串的n个后缀(suffix)从小到大排序. 何谓后缀?假设字符串是S=S1S2……Sn,定义Ti=SiSi+1……Sn.T1, T2, …, Tn就叫做S的n个后缀. 关于字符串大小的比较定义如下(比较规则和PASCAL中的定义完全相同,熟悉PASCAL的同学可以跳过此段): 若A是B的前缀,则A<B:否则令p满足:A1A2…Ap-1=B1B2…Bp-1,

【字符串】后缀排序

后缀排序 Task Description 给定一个字符串,要求按字典序升序输出它的所有后缀子串的第一个字符所在位置. Requirements & Limitations 字符集大小为常数,要求时间复杂度 \(O(n \log n)\),其中 \(n\) 为字符串长度 Algorithm 这就是大(ren)名(lei)鼎(zhi)鼎(hui)的后缀排序了.这里记录倍增法. 定义后缀 \(i\) 为首字符所在原字符串位置为 \(i\) 的后缀子串. 定义 \(sa_i\) 为排名为 \(i\)

后缀排序(codevs 1500)

题目描述 Description 天凯是MIT的新生.Prof. HandsomeG给了他一个长度为n的由小写字母构成的字符串,要求他把该字符串的n个后缀(suffix)从小到大排序. 何谓后缀?假设字符串是S=S1S2--Sn,定义Ti=SiSi+1--Sn.T1, T2, -, Tn就叫做S的n个后缀. 关于字符串大小的比较定义如下(比较规则和PASCAL中的定义完全相同,熟悉PASCAL的同学可以跳过此段): 若A是B的前缀,则A<B:否则令p满足:A1A2-Ap-1=B1B2-Bp-1,

hdu3518 Boring Counting[后缀排序]

裸的统计不同的重复出现子串(不重叠)种数的题.多次使用后缀排序要注意小细节.y数组在重复使用时一定要清空,看那个line25 +k就明白了 ,cnt也要清空,为什么就不说了 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 5 temp

洛谷P3809 【模板】后缀排序

题目背景 这是一道模板题. 题目描述 读入一个长度为 nn 的由大小写英文字母或数字组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置.位置编号为 11 到 nn . 输入输出格式 输入格式: 一行一个长度为 nn 的仅包含大小写英文字母或数字的字符串. 输出格式: 一行,共n个整数,表示答案. 输入输出样例 输入样例#1: 复制 ababa 输出样例#1: 复制 5 3 1 4 2 说明 n <= 10^6n<=106 更新了一下自己