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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

【2012百度之星/资格赛】H:用户请求中的品牌 [后缀数组]

發布時間:2023/11/27 生活经验 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【2012百度之星/资格赛】H:用户请求中的品牌 [后缀数组] 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
時間限制:
1000ms
內存限制:
65536kB
描述

餡餅同學是一個在百度工作,做用戶請求(query)分析的同學,他在用戶請求中經常會遇到一些很奇葩的詞匯。在比方說“johnsonjohnson”、“duckduck”,這些詞匯雖然看起來是一些詞匯的單純重復,但是往往都是一些特殊品牌的詞匯,不能被拆分開。為了偵測出這種詞的存在,你今天需要完成我給出的這個任務——“找出用戶請求中循環節最多的子串”。

輸入
輸入數據包括多組,每組為一個全部由小寫字母組成的不含空格的用戶請求(字符串),占一行。用戶請求的長度不大于100,000。
最后一行輸入為#,作為結束的標志。
輸出
對于每組輸入,先輸出這個組的編號(第n組就是輸出“Case n:”);然后輸出這組用戶請求中循環節最多的子串。如果一個用戶請求中有兩個循環節數相同的子串,請選擇那個字典序最小的。
樣例輸入
ilovejohnsonjohnsonverymuch
duckduckgo
aaabbbcccisagoodcompany
#
樣例輸出
Case 1: johnsonjohnson
Case 2: duckduck
Case 3: aaa

后綴數組的相關介紹:

后綴數組是處理字符串的有力工具。后綴數組是后綴樹的一個非常精巧的替代品,它比后綴樹容易編程實現,能夠實現后綴樹的很多功能而時間復雜度也并不遜色,而且它比后綴樹所占用的內存空間小很多??梢哉f,后綴數組比后綴樹要更為實用。自從拜讀了羅穗騫大牛的WC2009論文《后綴數組——處理字符串的有力工具》后,經過若干星期的努力(中間有因某些原因而緩下來),終于把論文上面的練習題全部完成了,現在寫寫自己對后綴數組的理解和感悟。在看本筆記時,請不要忘記了,這是筆記,而教材是《后綴數組——處理字符串的有力工具》。

一:后綴數組的實現

1、定義:Suffix Array數組(SA數組)用于保存從小到大排好序之后的后綴。RANK名次數組用來保存后綴S[i..n]在所有后綴中是第幾小的后綴。簡單來說,SA數組表示的是“排第幾的是誰”,RANK數組表示的是“你的排名是多少”。

2、求SA數組以及RANK數組的方法:詳細的請轉到羅穗騫大牛的論文,我的學習筆記重點不是要介紹這個。

3、對DA(倍增算法)的一些個人理解:由于我只學習了倍增算法,所以我只能談談我對它的理解。DC3算法我沒有去研究....

DA算法我是根據羅穗騫的模板寫的,根據自己的理解做了些許的小優化。我們現在來看看羅穗騫大牛的模板:

int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
int cmp(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
void da(int *r,int *sa,int n,int m)
{
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++) ws[i]=0;
for(i=0;i<n;i++) ws[x[i]=r[i]]++;
for(i=1;i<m;i++) ws[i]+=ws[i-1];
for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i;
for(j=1,p=1;p<n;j*=2,m=p)
{
for(p=0,i=n-j;i<n;i++) y[p++]=i;
for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0;i<n;i++) wv[i]=x[y[i]];
for(i=0;i<m;i++) ws[i]=0;
for(i=0;i<n;i++) ws[wv[i]]++;
for(i=1;i<m;i++) ws[i]+=ws[i-1];
for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
return;
}

其實,我個人認為,對于這個算法以及代碼,無需過分深入地理解,只需記憶即可,理解只是為了幫助記憶罷了。先解釋變量:n為字符串長度,m為字符的取值范圍,r為字符串。后面的j為每次排序時子串的長度。

for(i=0;i<m;i++) ws[i]=0;
for(i=0;i<n;i++) ws[x[i]=r[i]]++;
for(i=1;i<m;i++) ws[i]+=ws[i-1];
for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i;

這四行代碼,進行的是對R中長度為1的子串進行基數排序。x數組在后面需要用到,所以先復制r數組的值。特別需要注意的是,第四行的for語句,初始化語句為i=n-1,如果寫得不太熟練,很容易習慣性地寫成i=0,我一開始就是。理解這是基數排序的最好方法,找個例子,自己推推....

for(p=0,i=n-j;i<n;i++) y[p++]=i;
for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;

這兩行代碼,利用了上一次基數排序的結果,對待排序的子串的第二關鍵字進行了一次高效地基數排序。我們可以結合下面的圖來理解:



不難發現,除了第一次基數排序以外,之后的每次雙關鍵字排序,設此次排序子串長度為j,則從第n-j位開始的子串,其第二關鍵字均為0,所以得到第一個for語句:for(p=0,i=n-j;i<n;i++) y[p++]=i;使用pascal的朋友們注意了,這里之所以是n-j位,是因為c++的字符串是從第0位開始表示的。這里,p暫時成為了一個計數變量。第二個語句的意義,分析上圖也不難理解,這里留給朋友們你們自行思考啦。(不如說我懶...)

?

for(i=0;i<n;i++) wv[i]=x[y[i]];
for(i=0;i<m;i++) ws[i]=0;
for(i=0;i<n;i++) ws[wv[i]]++;
for(i=1;i<m;i++) ws[i]+=ws[i-1];
for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];


與一開始的4個for語句意義相同,基數排序。至于為什么wv[i]=x[y[i]],這個我想了蠻久沒想通...硬記算了- -哪位朋友理解的希望能告訴我一聲...

for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;


這個for語句中的初始化語句里,完成了x數組和y數組的交換,用了指針的交換節約時間,簡化代碼。這里需要注意的是p和i的初始值都是1,不是0.其實如果記得后面的語句,不難看出它們的初始值不能為0,因為后面有i-1和p-1嘛。這個for語句的意義要結合cmp函數來理解。反正,你知道這里p的值表示的是此時關鍵字不同的串的數量就對了。當p=n的時候,說明所有串都已經排好序了(它們的排名都唯一確定)。所以,一開始的循環語句中,循環條件是(p<n)。

另外,在使用倍增算法前,需要保證r數組的值均大于0。然后要在原字符串后添加一個0號字符,具體原因參見羅穗騫的論文。這時候,若原串的長度為n,則實際要進行后綴數組構建的r數組的長度應該為n+1.所以調用da函數時,對應的n應為n+1.

二、后綴數組的應用--height數組

在介紹后綴數組的應用前,先介紹后綴數組的一個重要附屬數組:height數組。

1、height 數組:定義height[i]=suffix(sa[i-1])和suffix(sa[i])的最長公
共前綴,也就是排名相鄰的兩個后綴的最長公共前綴。

height數組是應用后綴數組解題是的核心,基本上使用后綴數組解決的題目都是依賴height數組完成的。

2、height數組的求法:具體的求法參見羅穗騫的論文。對于height數組的求法,我并沒有去深刻理解,單純地記憶了而已...有興趣的朋友可以去鉆研鉆研再和我交流交流

這里給出代碼:

int rank[maxn],height[maxn];
void calheight(int *r,int *sa,int n)
{
int i,j,k=0;
for(i=1;i<=n;i++) rank[sa[i]]=i;
for(i=0;i<n;height[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
return;
}

3、一些注意事項:height數組的值應該是從height[1]開始的,而且height[1]應該是等于0的。原因是,因為我們在字符串后面添加了一個0號字符,所以它必然是最小的一個后綴。而字符串中的其他字符都應該是大于0的(前面有提到,使用倍增算法前需要確保這點),所以排名第二的字符串和0號字符的公共前綴(即height[1])應當為0.在調用calheight函數時,要注意height數組的范圍應該是[1..n]。所以調用時應該是calheight(r,sa,n)而不是calheight(r,sa,n+1)。要理解清楚這里的n的含義是什么。


calheight過程中,對rank數組求值的for語句的初始語句是i=1而不是i=0的原因,和上面說的類似,因為sa[0]總是等于那個已經失去作用的0號字符,所以沒必要求出其rank值。當然你錯寫成for (i=0..),也不會有什么問題。

三、后綴數組解題總結:

1、求單個子串的不重復子串個數。SPOJ 694、SPOJ 705.

這個問題是一個特殊求值問題。要認識到這樣一個事實:一個字符串中的所有子串都必然是它的后綴的前綴。(這句話稍微有點繞...)對于每一個sa[i]后綴,它的起始位置sa[i],那么它最多能得到該后綴長度個子串(n-sa[i]個),而其中有height[i]個是與前一個后綴相同的,所以它能產生的實際后綴個數便是n-sa[i]-height[i]。遍歷一次所有的后綴,將它產生的后綴數加起來便是答案。

代碼及題解:http://hi.baidu.com/fhnstephen/blog/item/68f919f849748668024f56fb.html

?

2、后綴的最長公共前綴。(記為lcp(x,y))

這是height數組的最基本性質之一。具體的可以參看羅穗騫的論文。后綴i和后綴j的最長公共前綴的長度為它們在sa數組中所在排位之間的height值中的最小值。這個描述可能有點亂,正規的說,令x=rank[i],y=rank[j],x<y,那么lcp(i,j)=min(height[x+1],height[x+2]...height[y])。lcp(i,i)=n-sa[i]。解決這個問題,用RMQ的ST算法即可(我只會這個,或者用最近公共祖先那個轉化的做法)。

3、最長重復子串(可重疊)

要看到,任何一個重復子串,都必然是某兩個后綴的最長公共前綴。因為,兩個后綴的公共前綴,它出現在這兩個后綴中,并且起始位置時不同的,所以這個公共前綴必然重復出現兩次以上(可重疊)。而任何兩個后綴的最長公共前綴為某一段height值中的最小值,所以最大為height值中的最大值(即某個lcp(sa[i],sa[i+1]))。所以只要算出height數組,然后輸出最大值就可以了。

一道題目和代碼:http://hi.baidu.com/fhnstephen/blog/item/4ed09dffdec0a78eb801a0ba.html

4、最長重復不重疊子串 PKU1743

這個問題和3的唯一區別在于能否重疊。加上不能重疊這個限制后,直接求解比較困難,所以我們選擇二分枚舉答案,將問題轉換為判定性問題。假設當時枚舉的長度為k,那么要怎樣判斷是否存在長度為k的重復不重疊子串呢?

首先,根據height數組,將后綴分成若干組,使得每組后綴中,后綴之間的height值不小于k。這樣分組之后,不難看出,如果某組后綴數量大于1,那么它們之中存在一個公共前綴,其長度為它們之間的height值的最小值。而我們分組之后,每組后綴之間height值的最小值大于等于k。所以,后綴數大于1的分組中,有可能存在滿足題目限制條件的長度不小于k的子串。只要判斷滿足題目限制條件成立,那么說明存在長度至少為k的合法子串。

對于本題,限制條件是不重疊,判斷的方法是,一組后綴中,起始位置最大的后綴的起始位置減去起始位置最小的后綴的起始位置>=k。滿足這個條件的話,那么這兩個后綴的公共前綴不但出現兩次,而且出現兩次的起始位置間隔大于等于k,所以不會重疊。

深刻理解這種height分組方法以及判斷重疊與否的方法,在后面的問題中起到舉足輕重的作用。

練習及題解:http://hi.baidu.com/fhnstephen/blog/item/85a25b208263794293580759.html

5、最長的出現k次的重復(可重疊)子串。 PKU3261

使用后綴數組解題時,遇到“最長”,除了特殊情況外(如問題3),一般需要二分答案,利用height值進行分組。本題的限制條件為出現k次。只需判斷,有沒有哪一組后綴數量不少于k就可以了。相信有了我前面問題的分析作為基礎,這個應該不難理解。注意理解“不少于k次”而不是“等于k次”的原因。如果理解不了,可以找個具體的例子來分析分析。

題目及題解:http://hi.baidu.com/fhnstephen/blog/item/be7d15133ccbe7f0c2ce79bb.html

6、最長回文子串 ural1297

這個問題沒有很直接的方法可以解決,但可以采用枚舉的方法。具體的就是枚舉回文子串的中心所在位置i。注意要分回文子串的長度為奇數還是偶數兩種情況分析。然后,我們要做的,是要求出以i為中心的回文子串最長為多長。利用后綴數組,可以設計出這樣一種求法:求i往后的后綴與i往前的前綴的最長公共前綴。我這里的表述有些問題,不過不影響理解。

要快速地求這個最長前綴,可以將原串反寫之后接在原串后面。在使用后綴數組的題目中,連接兩個(n個)字符串時,中間要用不可能會出現在原串中,不一樣的非0號的字符將它們隔開。這樣可以做到不影響后綴數組的性質。然后,問題就可以轉化為求兩個后綴的最長公共前綴了。具體的細節,留給大家自己思考...(懶...原諒我吧,都打這么多字了..一個多小時了啊TOT)

題目及題解:http://hi.baidu.com/fhnstephen/blog/item/68342f1d5f9e3cf81ad576ef.html

?

7、求一個串最多由哪個串復制若干次得到 PKU2406

具體的問題描述請參考PKU2406.這個問題可以用KMP解決,而且效率比后綴數組好。

利用后綴數組直接解決本題也很困難(主要是,就算二分答案,也難以解決轉變而成的判定性問題。上題也是),但可以同過枚舉模板串的長度k(模板串指被復制的那個串)將問題變成一個后綴數組可以解決的判定性問題。首先判斷k能否被n整除,然后只要看lcp(1,k+1)(實際在用c寫程序時是lcp(0,k))是否為n-k就可以了。

為什么這樣就行了呢?這要充分考慮到后綴的性質。當lcp(1,k+1)=n-k時,后綴k+1是后綴1(即整個字符串)的一個前綴。(因為后綴k+1的長度為n-k)那么,后綴1的前k個字符必然和后綴k+1的前k個字符對應相同。而后綴1的第k+1..2k個字符,又相當于后綴k+1的前k個字符,所以與后綴1的前k個字符對應相同,且和后綴k的k+1..2k又對應相同。依次類推,只要lcp(1,k+1)=n-k,那么s[1..k]就可以通過自復制n/k次得到整個字符串。找出k的最小值,就可以得到n/k的最大值了。

題目及題解:http://hi.baidu.com/fhnstephen/blog/item/5d79f2efe1c3623127979124.html

8、求兩個字符串的最長公共子串。Pku2774、Ural1517

首先區分好“最長公共子串”和“最長公共子序列”。前者的子串是連續的,后者是可以不連續的。

對于兩個字符串的問題,一般情況下均將它們連起來,構造height數組。然后,最長公共子串問題等價于后綴的最長公共前綴問題。只不過,并非所有的lcp值都能作為問題的答案。只有當兩個后綴分屬兩個字符串時,它們的lcp值才能作為答案。與問題3一樣,本題的答案必然是某個height值,因為lcp值是某段height值中的最小值。當區間長度為1時,lcp值等于某個height值。所以,本題只要掃描一遍后綴,找出后綴分屬兩個字符串的height值中的最大值就可以了。判斷方法這里就不說明了,留給大家自己思考...

題目及題解:

http://hi.baidu.com/fhnstephen/blog/item/8666a400cd949d7b3812bb44.html

http://hi.baidu.com/fhnstephen/blog/item/b5c7585600cadfc8b645aebe.html

9、重復次數最多的重復子串 SPOJ 687,Pku3693

難度比較大的一個問題,主要是羅穗騫的論文里的題解寫得有點含糊不清。題目的具體含義可以去參考Pku3693.

又是一題難以通過二分枚舉答案解決的問題(因為要求的是重復次數),所以選擇樸素枚舉的方法。先枚舉重復子串的長度k,再利用后綴數組來求長度為k的子串最多重復出現多少次。注意到一點,假如一個字符串它重復出現2次(這里不討論一次的情況,因為那是必然的),那么它必然包含s[0],s[k],s[2*k]...之中的相鄰的兩個。所以,我們可以枚舉一個數i,然后判斷從i*k這個位置起的長度為k的字符串能重復出現多少次。判斷方法和8中的相似,lcp(i*k,(i+1)*k)/k+1。但是,僅僅這樣會忽略點一些特殊情況,即重復子串的起點不在[i*k]位置上時的情況。這種情況應該怎么求解呢?看下面這個例子:

aabababc

當k=2,i=1時,枚舉到2的位置,此時的重復子串為ba(注意第一位是0),lcp(2,4)=3,所以ba重復出現了2次。但實際上,起始位置為1的字符串ab出現次數更多,為3次。我們注意到,這種情況下,lcp(2,4)=3,3不是2的整數倍。說明當前重復子串在最后沒有多重復出現一次,而重復出現了部分(這里是多重復出現了一個b)。如果我這樣說你沒有看懂,那么更具體地:

sa[2]=bababc

sa[4]=babc

lcp=bab

現在注意到了吧,ba重復出現了兩次之后,出現了一個b,而a沒有出現。那么,不難想到,可以將枚舉的位置往前挪一位,這樣這個最后的b就能和前面的一個a構成一個重復子串了,而假如前挪的一位正好是a,那么答案可以多1。所以,我們需要求出a=lcp(i*k,(i+1)*k)%n,然后向前挪k-a位,再用同樣的方法求其重復出現的長度。這里,令b=k-a,只需要lcp(b,b+k)>=k就可以了。實際上,lcp(b,b+k)>=k時,lcp(b,b+k)必然大于等于之前求得的lcp值,而此時答案的長度只加1。沒有理解的朋友細細體會下上圖吧。

題目及題解:http://hi.baidu.com/fhnstephen/blog/item/870da9ee3651404379f0555f.html

?

10.多個串的公共子串問題 PKU3294

首先將串連接起來,然后構造height數組,然后怎么辦呢?

對,二分答案再判斷是否可行就行了。可行條件很直觀:有一組后綴,有超過題目要求的個數個不同的字符串中的后綴存在。即,假如題目要求要出現在至少k個串中,那么就得有一組后綴,在不同字符串中的后綴數大于等于k。

題目及題解:http://hi.baidu.com/fhnstephen/blog/item/49c3b7dec79ec5e377c638f1.html

?

11、出現或反轉后出現所有字符串中的最長子串 PKU1226

http://hi.baidu.com/fhnstephen/blog/item/7fead5020a16d2da267fb5c0.html

12、不重疊地至少兩次出現在所有字符串中的最長子串 spoj220?http://hi.baidu.com/fhnstephen/blog/item/1dffe1dda1c98754cdbf1a35.html

之所以把兩題一起說,因為它們大同小異,方法在前面的題目均出現過。對于多個串,連起來;反轉后出現,將每個字符串反寫后和原串都連起來,將反寫后的串和原串看成同一個串;求最長,二分答案后height分組;出現在所有字符串中(反寫后的也行),判斷方法和10一樣,k=n而已;不重疊見問題4,只不過這里對于每個字符串都要進行檢驗而已。

?

13、兩個字符串的重復子串個數。 Pku3415

我個人覺得頗有難度的一個問題。具體的題目描述參看Pku3415。

大家可以移步到這:http://hi.baidu.com/fhnstephen/blog/item/bf06d001de30fc034afb51c1.html

14、最后的總結

用后綴數組解題有著一定的規律可循,這是后綴的性質所決定的,具體歸納如下:

1、N個字符串的問題(N>1)

方法:將它們連接起來,中間用不會出現在原串中的,互不相同的,非0號字符分隔開。

2、無限制條件下的最長公共子串(重復子串算是后綴們的最長公共前綴)

方法:height的最大值。這里的無限制條件是對子串無限制條件。最多只能是兩個串的最長公共子串,才可以直接是height的最大值。

3、特殊條件下的最長子串

方法:二分答案,再根據height數組進行分組,根據條件完成判定性問題。三個或以上的字符串的公共子串問題也需要二分答案。設此時要驗證的串長度為len,特殊條件有:

3.1、出現在k個串中

條件:屬于不同字符串的后綴個數不小于k。(在一組后綴中,下面省略)

3.2、不重疊

條件:出現在同一字符串中的后綴中,出現位置的最大值減最小值大于等于len。

3.3、可重疊出現k次

條件:出現在同一字符串中的后綴個數大于等于k。若對于每個字符串都需要滿足,需要逐個字符串進行判斷。

4、特殊計數

方法:根據后綴的性質,和題目的要求,通過自己的思考,看看用后綴數組能否實現。一般和“子串”有關的題目,用后綴數組應該是可以解決的。

5、重復問題

知道一點:lcp(i,i+k)可以判斷,以i為起點,長度為k的一個字符串,它向后自復制的長度為多少,再根據具體題目具體分析,得出算法即可。

?

轉載于:https://www.cnblogs.com/XBWer/archive/2012/05/30/2524987.html

總結

以上是生活随笔為你收集整理的【2012百度之星/资格赛】H:用户请求中的品牌 [后缀数组]的全部內容,希望文章能夠幫你解決所遇到的問題。

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