一下排名均是在字典序下的排名
\(sa[i]\):排名為\(i\)的字尾的編號
\(rank[i]\):第\(i\)個字尾串的排名
有\(rank[sa[i]]=i\)和\(sa[rank[i]]=i\)
\(height[i]\):排名為\(i\)的字尾和排名為\(i-1\)的字尾的最長公共字首
模板:
#include #include #include using namespace std;
const int n = 1e6 + 5;
int n, m;
char s[n];
int sa[n], x[n], y[n], rk[n], height[n], c[n];
void get_sa()
}void get_height()
}int main()
\(1.\)求本質不同的子串的個數:
普通做法是列舉左右端點然後雜湊判重。考慮我們當前列舉到的左端點是\(l\),那麼我們右端點就要依次列舉\(l+1,l+1,...,n\),發現,其實就是列舉第\(l\)個字尾的所有字首。然後考慮如何判重,將第\(l\)個字尾的所有字首按字典序排序記為,通過\(height\)陣列我們知道排名相鄰的兩個串的最長公共字首,先記他們的長度依次是\(len_1,len_2,...,len_\),那麼第乙個穿可以產生\(len_1\)個前面沒有出現過的串(因為是第乙個,所以不需要判重),再考慮第二個串,很容易知道第二個串貢獻的不同的串個數為\(len_2-height[2]\),\(height[2]\)記錄的是第二個串和第乙個串的最長共字首,這部分在第乙個串中被統計過了,所以不需要再統計。綜上,所有的答案就是\(\sum_^nlen_i-height[i]\)就是答案。
例題:\(生成魔咒\)
題意:每次加入乙個字元,問此時本質不同的子串的數量
sol:正向加不好做,考慮先把所有字元加入後,翻轉(翻轉和正向的答案其實是一樣的)後從前往後依次刪掉字元然後求答案。假設翻轉後的串為\(s\),將\(s\)的所有字尾排序後,每次從前刪掉乙個字元是,相當於刪去乙個字尾,\(sa\)陣列利用鍊錶維護,假設刪掉的字尾排名為\(i\),那麼第\(i-1\)和第\(i+1\)的串的最長公共字首即\(height[i+1]=min(height[i],height[i+1])\)的。然後按照上面的方法求解即可。
#include #define ll long long
using namespace std;
const int n = 1e6 + 5;
int n, m;
int s[n],l[n],r[n];
int sa[n], x[n], y[n], rk[n], height[n], c[n];
void get_sa()
}void get_height()
}unordered_mapmp;
int main()
get_sa();
get_height();
ll res=0;
for(int i=1;i<=n;i++)
l[n+1]=n,r[0]=1;
for(int i=1;i<=n;i++) //從前往後刪
for(int i=n;i>=1;i--)cout
題意:給定乙個陣列,求所有本質不同的子段的最大值的和
sol:(字尾陣列+單調棧+rmq)
如果不考慮本質不同的限制,那麼就是直接列舉每個數作為左端點,然後利用單調棧找到右邊第乙個大於這個數的下標,計算貢獻即可(可以參考\(atcoder minimum sum\))。考慮限制要怎麼操作呢?
定義陣列\(nxt[i]\)為第\(i\)個數右邊第乙個比它大的數的下標。\(suf[i]\)表示從\(i\)開始的依次遞增的貢獻的字尾和,轉移是\(suf[i]=a[i]\times(nxt[i]-i)+suf[nxt[i]]\),什麼意思呢,如下圖!
按字典序排好序後,假設當前計算到第\(i\)個串,如果\(height[i]\)不等於,說明第\(i\)個串和第\(i-1\)個串有公共字首,如果直接加到答案裡會算重。設\(l=sa[i],r=sa[i]+height[i]-1\),即區間\([l,r]\)是重複的部分,設\(p\)是\([l,r]\)中值最大的數的下標,則\(nxt[p]\)一定大於\(r\),因為\(a[p]\)是\([l,r]\)中最大的了,而\(a[nxt[p]]>a[p]\),因此\(nxt[p]\)只能存在於比\(r\)大的地方。所以答案貢獻就是\(suf[nxt[p]]+a[p]\times (nxt[p]-r-1)\),為什麼是\(nxt[p]-r-1\)是因為第\(i-1\)個串已經計算了\([p+1,r]\)對數\(a[p]\)的貢獻,可能有人會覺得這樣這樣子會不會遺漏統計\([r+1,nxt[p-1]]\)對\([p+1,r]\)中的數的貢獻,其實不會,為什麼,因為我們計算的重複只是和第\(i-1\)個串比較,第\(i-1\)個串是不會統計\([p+1,r]\)中的數對答案的貢獻,所以我們在第\(i\)個串不需要考慮這部分。
題意
給定乙個長度為\(n\)的\(1\)到\(n\)的乙個排列,讓你計算\(\sum_^n\sum_^nmin(a_l,a_,...,a_r)\)
\(1\le n\le 200000\)
sol
考慮統計每乙個\(a_i\)作為貢獻的區間數量
很容易想到先找到\(a_i\)右邊第乙個比\(a_i\)小的數的下標\(r[i]\)和左邊第乙個比\(a_i\)小的數的下標\(l[i]\)。(經典單調棧操作)
比如這樣乙個序列\(1,4,3,6,9,2,7,8\)
考慮數字\(3\)下標為\(3\)的貢獻,則左邊第乙個比\(3\)小的數的下標為\(1\),則右邊第乙個比\(3\)小的數的下標為\(6\),那麼顯然有貢獻的區間為\([2,3],[3,3],[3,4],[3,5],[2,4],[2,5]\),由乘法原理知道有\((3-1)\times (6-3)\)個,即\((i-l[i])\times (r[i]-i)\)個。
字尾陣列複習小記
字尾陣列,顧名思義就是處理字尾的陣列。例如daabbc的字尾 daabbc,aabbc,abbc,bbc,bc,c六個字尾。定義sa i 為排名第i個的字尾的第乙個字元在原字串中的序號。如上面的sa 1 2,因為字尾從序號2開始的aabbc排第乙個。定義rank i 為序號從i開始的字尾排第幾個。如...
字尾陣列複習小記
定義 suf i 表示以i為開頭的字尾 rank i 表示suf i 的排名,sa i 表示排名為i的字尾 height i 表示sa i 和sa i 1 的lcp h i 表示suf i 和suf sa rank i 1 的lcp sa rank i i,所以只要能求出rank,就可以求sa 倍增...
bzoj3998 字尾陣列複習
對於乙個給定長度為n的字串,求它的第k小子串是什麼。其中可能有重複和不重複的k小子串。我只會字尾陣列 sa做法 當t 0的詢問,我們從sa i 開始,每次sa i 這個字尾,我們會加入n sa i 1個子串,其中height i 個是和sa i 1 重複的,所以我們記乙個變數每次加上n 1 heig...