1. 程式人生 > >P3809 【模板】後綴排序

P3809 【模板】後綴排序

兩個 一行 輸入格式 包含 inline 關鍵字 == class %d

\(\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;
}

P3809 【模板】後綴排序