zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    【C++】关于new分配空间
    【嵌入式】keil不识别野火高速dap的问题
    【Java】质数判断
    【学习方法】大学背知识点方法
    【算法】浮点数多次运算精确值下降
    【算法】main函数的堆栈溢出
    【算法】三值选中法
    【CC++笔记】register寄存器关键字
    JSON.parse()和JSON.stringify()
    webStorm使用和安装
  • 原文地址:https://www.cnblogs.com/olinr/p/10158778.html
Copyright © 2011-2022 走看看