P3809 【模板】後綴排序
阿新 • • 發佈:2018-12-21
兩個 一行 輸入格式 包含 inline 關鍵字 == class %d
假設通過一輪基數排序成功比較出了第一個字母\(O(n)\)
\(\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 【模板】後綴排序