日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

模板:后缀数组(SA)

發布時間:2023/12/3 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 模板:后缀数组(SA) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 前言
  • 解析
    • 后綴排序
      • 優化1:基數排序
      • 優化2:簡化第一次排序
      • 優化3:提前break
      • 完整代碼
    • LCP與height

所謂后綴數組,就是存儲后綴的數組

(逃)

前言

為什么一個算法,如此難以理解卻依然是成為一個成熟OIer不可回避的必修課?
足以可見后綴家族功能的強大

首先,由于其本身的性質,后綴數組對字典序相關的問題十分擅長
同時,由于 heightheightheight 數組的眾多優秀性質,它在處理公共串問題和 LCP 問題上也十分強大
(我目前SA的題加起來也沒做上十道,所以這樣的“總結”請選擇性閱讀)

解析

后綴排序

P3809 【模板】后綴排序

給出一個字符串,把所有后綴按照字典序排序
n≤106n\le10^6n106

考慮倍增
一開始子串長度為 111,每個位置的排名 rkirk_irki? 就是自己位置的字符
然后在已知長度為 www 的所有子串的排名的情況下,以 rki+wrk_{i+w}rki+w? 為第二關鍵字,rkirk_irki? 為第一關鍵字排序,可以得到長度為 2w2w2w 的所有子串的排名(空串的排名視為負無窮)
每次用 sort 的話,時間復雜度 O(nlog2n)O(nlog^2n^)O(nlog2n)

優化1:基數排序

注意到這里的排序是關于大小的排序,且值域(排名)只有 O(n)O(n)O(n)
所以我們可以使用基數排序代替 sort,時間復雜度變成 O(nlogn)O(nlogn)O(nlogn)

注意! 基數排序重新排列的循環必須倒序枚舉,這樣才能保證排序的穩定性

memset(cnt,0,sizeof(cnt)); memcpy(oldrk,rk,sizeof(rk)); for(int i=1;i<=n;i++) ++cnt[rk[id[i]]]; for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1]; for(int i=n;i>=1;i--) sa[cnt[rk[id[i]]]--]=id[i]; p=0; for(int i=1;i<=n;i++){if(oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+w]==oldrk[sa[i-1]+w]) rk[sa[i]]=p;else rk[sa[i]]=++p; } m=p;

優化2:簡化第一次排序

第一次是關于rkiwrk_{i_w}rkiw?? 排序
并不需要基數排序,只需要:

p=0; for(int i=n;i>n-w;i--) id[++p]=i; for(int i=1;i<=n;i++){if(sa[i]>w) id[++p]=sa[i]-w; }

即可

優化3:提前break

玄學優化
大概就是,不必真的倍增到總長度,只需要讓所有字符串的排名互相分開即可
這東西在全是 a 這樣的串中可以說是等于沒有,但在不少時候優化巨大(比如本題 2.2s→0.8s2.2s\to 0.8s2.2s0.8s)

完整代碼

saisa_isai?:排名為 iii 的后綴的編號
rkirk_irki?:后綴 iii 的排名

#include<bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned long long #define debug(...) fprintf(stderr,__VA_ARGS__) const int N=1e6+100; inline ll read(){ll x(0),f(1);char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f; } int n,m,k; char s[N]; int rk[N<<1],oldrk[N<<1],id[N],sa[N],cnt[N],p; void write(int x){if(x>9) write(x/10);putchar('0'+x%10);return; } signed main() { #ifndef ONLINE_JUDGE//freopen("a.in","r",stdin);//freopen("a.out","w",stdout); #endifscanf(" %s",s+1);n=strlen(s+1);m=122;for(int i=1;i<=n;i++) cnt[rk[i]=s[i]]++;for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];for(int i=n;i>=1;i--) sa[cnt[rk[i]]--]=i;for(int w=1;;w<<=1){p=0;for(int i=n;i>n-w;i--) id[++p]=i;for(int i=1;i<=n;i++){if(sa[i]>w) id[++p]=sa[i]-w;}memset(cnt,0,sizeof(cnt));memcpy(oldrk,rk,sizeof(rk));for(int i=1;i<=n;i++) ++cnt[rk[id[i]]];for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];for(int i=n;i>=1;i--) sa[cnt[rk[id[i]]]--]=id[i];p=0;for(int i=1;i<=n;i++){if(oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+w]==oldrk[sa[i-1]+w]) rk[sa[i]]=p;else rk[sa[i]]=++p;}m=p;if(m==n) break;//優化3}for(int i=1;i<=n;i++) write(sa[i]),putchar(' ');return 0; } /**/

LCP與height

定義:

height(i)height(i)height(i) 表示后綴 saisa_isai? 和后綴 sai?1sa_{i-1}sai?1? 的最長公共前綴(lcp(sai,sai?1)lcp(sa_i,sa_{i-1})lcp(sai?,sai?1?))。特別的,lcp(1)=0lcp(1)=0lcp(1)=0

感性理解來說,把所有后綴按照字典序排序后,height(i)height(i)height(i) 就是相鄰兩個后綴的相同部分的長度。

引理1:lcp(i,j)=min?(lcpi,k,lcpk,j)lcp(i,j)=\min (lcp_{i,k},lcp_{k,j})lcp(i,j)=min(lcpi,k?,lcpk,j?),對于任意的 i≤k≤ji\le k\le jikj 均成立.

證明:
首先,min?(lcpi,k,lcpk,j)\min (lcp_{i,k},lcp_{k,j})min(lcpi,k?,lcpk,j?)kkki,ji,ji,j 共同的公共前綴,所以也必然是 i,ji,ji,j 的公共前綴,lcp(i,j)≥min?(lcpi,k,lcpk,j)lcp(i,j)\ge\min (lcp_{i,k},lcp_{k,j})lcp(i,j)min(lcpi,k?,lcpk,j?)。
同時,由于字典序單調的性質,iii 變到 kkk 變化的前綴在 kkk 變化到 jjj 時必然不可能再變回來,否則 jjj 的字典序就比 kkk 小了,所以有 lcp(i,j)≤min?(lcpi,k,lcpk,j)lcp(i,j)\le\min (lcp_{i,k},lcp_{k,j})lcp(i,j)min(lcpi,k?,lcpk,j?)。
綜上,lcp(i,j)=min?(lcpi,k,lcpk,j)lcp(i,j)=\min (lcp_{i,k},lcp_{k,j})lcp(i,j)=min(lcpi,k?,lcpk,j?),證畢。

引理2:heightrki≥heightrki?1?1height_{rk_i}\ge height_{rk_{i-1}}-1heightrki??heightrki?1???1

證明:
rki?1≤1rk_{i-1}\le1rki?1?1 時,顯然成立
rki?1>1rk_{i-1}>1rki?1?>1 時,設 rki?1?1=krk_{i-1}-1=krki?1??1=kkkk 就是 i?1i-1i?1 按字典序排序后的前一個),那么:
i?1i-1i?1kkk 的首字母不同, hi?1=0h_{i-1}=0hi?1?=0 ,顯然成立
i?1i-1i?1kkk 的首字母相同,那么考慮字符串 k+1k+1k+1,由于k 去掉首字符變成 k+1,i-1 去掉首字母變成 i,所以 k+1k+1k+1 也一定在 iii 的前面,同時 lcp(k+1,i)=lcp(k,i?1)?1=heightrki?1?1lcp(k+1,i)=lcp(k,i-1)-1=height_{rk{i-1}}-1lcp(k+1,i)=lcp(k,i?1)?1=heightrki?1??1,由引理1,有 lcp(k+1,i)=min?(lcp(k+1,rki?1),lcp(i?1,i))lcp(k+1,i)=\min (lcp(k+1,rk_i-1),lcp(i-1,i))lcp(k+1,i)=min(lcp(k+1,rki??1),lcp(i?1,i)),故 lcp(i?1,i)≥heightrki?1?1lcp(i-1,i)\ge height_{rk{i-1}}-1lcp(i?1,i)heightrki?1??1,即 heightrki≥heightrki?1?1height_{rk_i}\ge height_{rk_{i-1}}-1heightrki??heightrki?1???1
得證。
得出這個性質后,線性求 heightheightheight 的代碼就不難寫出了:

for(int i=1,k=0;i<=n;i++){if(k) --k;while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;ht[rk[i]]=k; }

Thanks for reading!

總結

以上是生活随笔為你收集整理的模板:后缀数组(SA)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。