五分鐘搞懂后綴數組!

為什么學后綴數組

后綴數組是一個比較強大的處理字符串的算法,是有關字符串的基礎算法,所以必須掌握。?
學會后綴自動機(SAM)就不用學后綴數組(SA)了?不,雖然SAM看起來更為強大和全面,但是有些SAM解決不了的問題能被SA解決,只掌握SAM是遠遠不夠的。?
……

有什么SAM做不了的例子??
比如果求一個串后綴的lcp方面的應用,這是SA可以很方便的用rmq來維護,但是SAM還要求lca,比較麻煩,還有就是字符集比較大的時候SA也有優勢。

現在這里放道題,看完這個blog可能就會做了!:?
你可想想這道題:你有一個01串S,然后定義一個前綴最右邊的位置就是這個前綴的結束位置。現在有q多個詢問,每個詢問結束位置在l~r中不同前綴的最長公共后綴是多長??
|S|,q100000|S|,q≤100000?
時限4s

而下面是我對后綴數組的一些理解

構造后綴數組——SA

先定義一些變量的含義

Str :需要處理的字符串(長度為Len)?
Suffix[i] :Str下標為i ~ Len的連續子串(即后綴)?
Rank[i] : Suffix[i]在所有后綴中的排名?
SA[i] : 滿足Suffix[SA[1]] < Suffix[SA[2]] …… < Suffix[SA[Len]],即排名為i的后綴為Suffix[SA[i]] (與Rank是互逆運算)?
好,來形象的理解一下?
這就是Rank和SA
后綴數組指的就是這個SA[i],有了它,我們就可以實現一些很強大的功能(如不相同子串個數、連續重復子串等)。如何快速的到它,便成為了這個算法的關鍵。而SA和Rank是互逆的,只要求出任意一個,另一個就可以O(Len)得到。?

現在比較主流的算法有兩種,倍增和DC3,在這里,就主要講一下稍微慢一些,但比較好實現以及理解的倍增算法(雖說慢,但也是O(Len logLen))的。

進入正題——倍增算法

倍增算法的主要思想?:對于一個后綴Suffix[i],如果想直接得到Rank比較困難,但是我們可以對每個字符開始的長度為2k2k的字符串求出排名,k從0開始每次遞增1(每遞增1就成為一輪),當2k2k大于Len時,所得到的序列就是Rank,而SA也就知道了。O(logLen)枚舉k?
這樣做有什么好處呢??
設每一輪得到的序列為rank(注意r是小寫,最終后綴排名Rank是大寫)。有一個很美妙的性質就出現了!第k輪的rank可由第k - 1輪的rank快速得來!?
為什么呢?為了方便描述,設SubStr(i, len)為從第i個字符開始,長度為len的字符串我們可以把第k輪SubStr(i,?2k2k)看成是一個由SubStr(i,?2k?12k?1)和SubStr(i +?2k?12k?1,?2k?12k?1)拼起來的東西。類似rmq算法,這兩個長度而2k?12k?1的字符串是上一輪遇到過的!當然上一輪的rank也知道!那么吧每個這一輪的字符串都轉化為這種形式,并且大家都知道字符串的比較是從左往右,左邊和右邊的大小我們可以用上一輪的rank表示,那么……這不就是一些兩位數(也可以視為第一關鍵字和第二關鍵字)比較大小嗎!再把這些兩位數重新排名就是這一輪的rank。?
我們用下面這張經典的圖理解一下:?
就像一個兩位數的比較
相信只要理解字符串的比較法則(跟實數差不多),理解起來并不難。#還有一個細節就是怎么把這些兩位數排序?這種位數少的數進行排序毫無疑問的要用一個復雜度為長度*排序數的個數的優美算法——基數排序(對于兩位數的數復雜度就是O(Len)的)。?
基數排序原理?: 把數字依次按照由低位到高位依次排序,排序時只看當前位。對于每一位排序時,因為上一位已經是有序的,所以這一位相等或符合大小條件時就不用交換位置,如果不符合大小條件就交換,實現可以用”桶”來做。(敘說起來比較奇怪,看完下面的代碼應該更好理解,也可以上網查有關資料)?
好了SA和Rank(大寫R)到此為止就處理好了。(下面有詳解代碼!)。但我們發現,只有這兩樣東西好像沒什么用,為了處理重復子串之類的問題,我們就要引入一個表示最長公共前綴的新助手Height數組!

構造最長公共前綴——Height

同樣先是定義一些變量

Heigth[i] : 表示Suffix[SA[i]]和Suffix[SA[i - 1]]的最長公共前綴,也就是排名相鄰的兩個后綴的最長公共前綴?
H[i] : 等于Height[Rank[i]],也就是后綴Suffix[i]和它前一名的后綴的最長公共前綴?
而兩個排名不相鄰的最長公共前綴定義為排名在它們之間的Height的最小值。?
跟上面一樣,先形像的理解一下:?
這就是Height

高效地得到Height數組

如果一個一個數按SA中的順序比較的話復雜度是O(N2N2)級別的,想要快速的得到Height就需要用到一個關于H數組的性質。?
H[i] ≥ H[i - 1] - 1!?
如果上面這個性質是對的,那我們可以按照H[1]、H[2]……H[Len]的順序進行計算,那么復雜度就降為O(N)了!?
讓我們嘗試一下證明這個性質?: 設Suffix[k]是排在Suffix[i - 1]前一名的后綴,則它們的最長公共前綴是H[i - 1]。都去掉第一個字符,就變成Suffix[k + 1]和Suffix[i]。如果H[i - 1] = 0或1,那么H[i] ≥ 0顯然成立。否則,H[i] ≥ H[i - 1] - 1(去掉了原來的第一個,其他前綴一樣相等),所以Suffix[i]和在它前一名的后綴的最長公共前綴至少是H[i - 1] - 1。?
仔細想想還是比較好理解的。H求出來,那Height就相應的求出來了,這樣結合SA,Rank和Height我們就可以做很多關于字符串的題了!

代碼——Code

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 const int N=100010;
 6 int n,m,rank[N],sa[N],buc[N],id[N],height[N]; 
 7 char s[N];
 8 /*------------------------------------------------------------
 9 rank[i] 第i個后綴的排名; 
10 sa[i] 排名為i的后綴位置; 
11 buc[i] 計數排序輔助數組;
12 height[i] 排名為i的后綴與排名為(i-1)的后綴的LCP;
13 id[i] 倍增中后半段字符串位置(計數排序中的第二關鍵字);
14 s為原串
15 ------------------------------------------------------------*/
16 inline void qsort(){
17     //rank第一關鍵字,id第二關鍵字。
18     for(int i=0;i<=m;++i) buc[i]=0;
19     for(int i=1;i<=n;++i) ++buc[rank[id[i]]];
20     for(int i=1;i<=m;++i) buc[i]+=buc[i-1];
21     for(int i=n;i>=1;--i) sa[buc[rank[id[i]]]--]=id[i]; 
22     //計數排序,把新的二元組排序
23 }
24 //通過二元組兩個下標的比較,確定兩個子串是否相同
25 inline int cmp(int x,int y,int l){return id[x]==id[y]&&id[x+l]==id[y+l];}
26 int main() {
27     scanf("%s",s+1);
28     n=strlen(s+1);
29     for(int i=1;i<=n;++i) rank[i]=s[i],id[i]=i;
30     m=127,qsort();//一開始是以單個字符為單位,所以(m = 127)
31     for(int l=1,p=1,i;p<n;l<<=1,m=p){
32         //l 當前一個子串的長度; m 當前離散后的排名種類數
33         //當前的id(第二關鍵字)可直接由上一次的sa的得到
34         //更新sa值,并用id暫時存下上一輪的rank(用于cmp比較)
35         for(p=0,i=n-l+1;i<=n;++i) id[++p]=i;//長度越界,第二關鍵字為0
36         for(i=1;i<=n;++i) if (sa[i]>l) id[++p]=sa[i]-l;
37         qsort(),swap(rank,id),rank[sa[1]]=p=1;
38         //用已經完成的SA來更新與它互逆的rank,并離散rank
39         for(i=2;i<=n;++i) rank[sa[i]]=cmp(sa[i],sa[i-1],l)?p:++p;
40     }
41     //LCP  這個知道原理后就比較好理解程序
42     int j,k=0;
43     for(int i=1;i<=n;height[rank[i++]]=k)
44     for(k=k?k-1:k,j=sa[rank[i]-1];s[i+k]==s[j+k];++k);
45     return 0;
46 }
Code

4個比較基礎的應用

Q1:一個串中兩個串的最大公共前綴是多少??
A1:這不就是Height嗎?用rmq預處理,再O(1)查詢。?
?
Q2:一個串中可重疊的重復最長子串是多長??
A2:就是求任意兩個后綴的最長公共前綴,而任意兩個后綴的最長公共前綴都是Height 數組里某一段的最小值,那最長的就是Height中的最大值。?
?
Q3:一個串種不可重疊的重復最長子串是多長??
A3:先二分答案,轉化成判別式的問題比較好處理。假設當前需要判別長度為k是否符合要求,只需把排序后的后綴分成若干組,其中每組的后綴之間的Height 值都不小于k,再判斷其中有沒有不重復的后綴,具體就是看最大的SA值和最小的SA值相差超不超過k,有一組超過的話k就是合法答案。?
?
A4:一個字符串不相等的子串的個數是多少??
Q4:每個子串一定是某個后綴的前綴,那么原問題等價于求所有后綴之間的不相同的前綴的個數。而且可以發現每一個后綴Suffix[SA[i]]的貢獻是Len - SA[i] + 1,但是有子串算重復,重復的就是Heigh[i]個與前面相同的前綴,那么減去就可以了。最后,一個后綴Suffix[SA[i]]的貢獻就是Len - SA[k] + 1 - Height[k]。?
對于后綴數組更多的應用這里就不詳細闡述,經過思考后每個人都會發現它的一些不同的用途,它的功能也許比你想象中的更強大!

最開始的那道題

先搬下來。。。

你可想想這道題:你有一個01串S,然后定義一個前綴最右邊的位置就是這個前綴的結束位置。現在有很多個詢問,每q個詢問結束位置在l~r中不同前綴的最長公共后綴是多長??
|S|,q100000|S|,q≤100000?
時限4s

簡單思路:首先可以把字符串反過來就是求后綴的最長公共前綴了,可以用SA求出height數組,然后用rmq預處理之后就是求兩個位置間的最小值。然后對于一個區間,顯然只有在SA數組中相鄰的兩個串可以貢獻答案。?
對于區間詢問的問題可以用莫隊處理,然后考慮加入一個后綴應該怎么處理,我們可以維護一個按SA數組排序的鏈表。假設我們先把所有位置的SA全部加入,然后按順序刪除,重新按順序加入時就可以O(1)完成修改。那么按照這個思路我們可以用固定左端點的并查集,做到只加入,不刪除,然后用O(nn??√+nlogn)O(nn+nlogn)的復雜度完成這道題。

*可能后面的處理方式比較麻煩,如果直接用splay維護區間中的后綴的話可以做到O(nn??√logn)O(nnlogn),這個方法就比較直觀,而SAM在個問題上還是有點無力的。這題只是為了說明SA相比于SAM還是有他的獨到之處,特別是在處理后綴的lcp之類的問題上。

結束

?

轉載于:https://www.cnblogs.com/Sparks-Pion/p/9558888.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/249612.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/249612.shtml
英文地址,請注明出處:http://en.pswp.cn/news/249612.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

spring-core

spring最核心的組件是BeanFactory&#xff0c;看了源碼才發現&#xff0c;BeanFactory并非定義在spring-core中&#xff0c;那spring-core都有啥東東&#xff1f; spring-core主要提供以下服務&#xff0c;為BeanFactory的定義提供基礎服務。 1, ConversionService Conversi…

nginx配置靜態文件過期時間

1. 編輯虛擬主機配置文件/usr/local/nginx/conf/vhosts/huangzhenping.conf 說明&#xff1a;采用location方式 12345678910location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)${access_log off;expires 1d;}location ~ \.(js|css){access_log off;expires 1d;}2. 檢查配置文件&#x…

vue 移動端在div上綁定click事件 失效

在.vue的文件中使用了better-scroll&#xff0c;在div標簽上綁定click事件后&#xff0c;無效。 原因&#xff1a;使用了better-scroll&#xff0c;默認它會阻止touch事件。所以在配置中需要加上click: true 即可解決 mounted(){this.$nextTick(() > {let bscrollDom this.…

Java中的鉤子方法

鉤子方法是啥 鉤子顧名思義就是用來掛東西的。那么要掛東西必須有個被掛的東西&#xff0c;要不就是鐵環、要不就是墻的邊沿。所以要能掛住東西必須要有個被勾住的鐵環&#xff0c;要一個鉤子。那么在java中也是同樣的原理&#xff0c;你首先需要一個被掛在的東西&#xff0c;一…

啟動tomcat出現too many connections的原因及解決方法

感謝分享&#xff0c;原文地址&#xff1a;http://blog.sina.com.cn/s/blog_e7e07ec30102vsba.html一、原因 產生too many connections 的直接原因是因為數據庫提供的連接被全部占滿了。數據庫可以提供多少連接&#xff0c;可以再my.cnf(linux)或者my.ini(windows)下設定。這個…

Spring Beans 初始化流程分析

測試用例 依然使用這個官網上的用例&#xff0c;來進行調試&#xff1b; Person.java package org.shangyang.spring.container;/**- - author shangyang**/public class Person {String name;Person spouse;public String getName() {return name;}public void setName(Stri…

劍指offer(65)矩陣中的路徑

題目描述 請設計一個函數&#xff0c;用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一個格子開始&#xff0c;每一步可以在矩陣中向左&#xff0c;向右&#xff0c;向上&#xff0c;向下移動一個格子。如果一條路徑經過了矩陣中的某一個…

VSCode中怎么改變文件夾的圖標

昨天更新了VSCode后我的文件夾圖標莫名其妙的沒有了&#xff0c;變成了下圖這樣 看著真的讓我難受的頭皮發麻&#xff0c;本來打代碼就頭發少&#xff0c;難道非要讓我變成禿頭&#xff0c;不可能不可能&#xff0c;所以我找了找怎么解決 來&#xff0c;各位看官上眼 如圖所示 …

jdk1.8以前不建議使用其自帶的Base64來加解密

JDK1.8之前的base64是內部測試使用的代碼&#xff0c;不建議生產環境使用&#xff0c;而且未來可能會移除&#xff0c; JDK1.8提供最新可以正式使用的Base64類&#xff0c; 不要使用JDK中自帶的sun.misc.BASE64Decoder這個類去BASE64&#xff0c; 這個會在后面多加換行。使用ap…

Redis的五大數據類型

1.String&#xff08;字符串&#xff09; String是Redis最基本的類型&#xff0c;一個Key對應一個Value。 String類型是二進制安全的&#xff0c;意思是Redis的String可以包含任何數據&#xff0c;比如jpg圖片或者序列化的對象。 String類型是Redis最基本的數據類型&#xff0c…

springxml解析

1.XML驗證模式的認識 首先XML的驗證模式有兩種&#xff1a;DTD和XSD。 DTD文檔類型定義&#xff0c;是XML約束模式語言。它是為了保證XML文檔格式正確有效的方法。通過XML文檔和DTD文檔的比較來判斷XML是否符合規范。(現在我很少見&#xff0c;不知道是不是淘汰了) 舉個例子&…

jq函數綁定與解綁

最近學到幾個新的jq函數 1、bind&#xff08;&#xff09;綁定函數 2、unbind&#xff08;&#xff09;解綁函數 3、add() .給元素追加字符串 4、addClass() 給某元素增加class屬性值轉載于:https://www.cnblogs.com/bigwang1126/p/9566556.html

微信小程序時間標簽與范圍聯動設計實現

微信小程序時間標簽與范圍聯動設計實現&#xff1f;最近忙于一個有關數據管理的微信小程序開發&#xff0c;遇到了上圖情況&#xff0c;雖然很簡單&#xff0c;還是整理一下。若有錯誤&#xff0c;請廣大朋友們指正。 使用微信小程序組件radio-group、picker&#xff0c;用wxss…

github中的watch、star、fork的作用

在每個 github 項目的右上角&#xff0c;都有三個按鈕,分別是 watch、star、fork&#xff0c;但是有些剛開始使用 github 的同學&#xff0c;可能對這三個按鈕的使用卻不怎么了解&#xff0c;包括一開始使用 github 的我也是如此&#xff0c;這篇博客&#xff0c;結合自己的理解…

docker 操作 記錄

docker ps #查看當前docker容器 docker exec -it 容器名稱 sh 進入docker容器 docker stop 停止docker容器轉載于:https://www.cnblogs.com/objects/p/9569299.html

關于群論證明費馬小定理?

這篇博客就是講證費馬的&#xff0c;沒什么意思。 既然是要用群論證明費馬小定理&#xff0c;那么我們先用數論證明一下。 (以下的 p 為一個質數) 首先我們考慮 一個前置定理&#xff1a; 第一個證明 若 $(c,p) 1$ (即 c 與 p 的 gcd 為 1)&#xff0c;且 $ac ≡ bc (mod\ p)$ …

spring 源碼-context

1 spring-context 模塊概要 該模塊主要實現在spring-beans 模塊的擴展&#xff0c;主要對aop支持及el表達式的實現 分析示例 public static void main(String[] args){ClassPathXmlApplicationContext context new ClassPathXmlApplicationContext("spring-aop.xml"…

標示符和關鍵字的總結--希望別再犯錯

&#xff08;一&#xff09;Java關鍵字的表 一共50個關鍵字&#xff0c;如下表 其中絕大部分關鍵詞是Java語法發布之初就約定好的&#xff0c;少部分關鍵詞是隨Java語言發展后加入的。 strictfp JDK1.2 加入 assert JDK1.4 加入 enum JDK5.0 加入 還有少數單詞&#xff0c;目前…

歷屆試題 打印十字圖

問題描述 小明為某機構設計了一個十字型的徽標&#xff08;并非紅十字會啊&#xff09;&#xff0c;如下所示&#xff1a; ..$$$$$$$$$$$$$....$...........$..$$$.$$$$$$$$$.$$$$...$.......$...$$.$$$.$$$$$.$$$.$$.$...$...$...$.$$.$.$$$.$.$$$.$.$$.$.$...$...$.$.$$.$.$.…

Spring BeanDefinition

BeanDefinition&#xff0c;顧名思義&#xff0c;是一個對象(Bean)在Spring中描述&#xff0c;其核心類圖&#xff1a; 從類圖我們詳細了解BeanDefinition。 BeanDefinition接口繼承自BeanMetadataElement和AttributeAccessor兩個接口。 BeanMetadataElement&#xff1a;bean…