解題報告 (三) Manacher算法

迴文子串

        迴文分爲奇數迴文和偶數迴文,即字符串的長度爲奇數還是偶數。其實只需要討論奇數的情況,即以某個字符爲中心,左右兩邊的字符按照中心對稱的情況(偶數的情況可以通過本文末尾的方法轉換成奇數的情況)。

 

最長迴文子串

        樸素算法

        枚舉每個字符,作爲中心,向兩邊擴展判斷是否相等,找到最大的可行長度。算法最壞複雜度O(n^2)。

圖1

        Manacher算法

        用p[i]表示以第i個字符爲中心軸,兩邊字符軸對稱的最大半徑。如圖所示,p[1]=1,p[2]=2,p[4]=3,p[7]=5。主體思想還是和樸素算法類似,從小到大枚舉i,計算p[i]。核心思想在於p[i]的初始值,它不再是1,而是根據之前計算的{p[j] | 1<=j<i}來確定p[i]的初始值。

圖2

 

        我們用一個變量ct來保存前面所有字符產生的最大回文子串的“最大右邊界”的中心軸位置,“最大右邊界”的含義不是p[i]最大,而是i + p[i]最大,如圖2中枚舉完前8個位置後,得到ct=7,對應的右邊界爲ct + p[ct] - 1。

        然後,我們有了一些初始信息,那麼當枚舉到第i個字符時,分情況討論:

        1)如圖3所示,如果i<ct+p[ct],即第i個字符必然落在以ct爲中心軸的最大回文子串之內。那麼我們必然可以找到一個和i點按照ct軸對稱的點j,滿足(i+j)/2=ct,則j = 2*ct-i。

圖3

 

        這時候,我們繼續分情況討論,j的位置可能衍生出三種情況,如圖4所示:

圖4

 

        (a)的情況,p[i]=p[j],而且不可能再長。假設p[i]能夠再長,則根據對稱性,p[j]也能夠再長,但是p[j]已經確定了,所以假設不成立;

        (b)的情況,p[i]=p[j],和(a)不同的是,它有可能還可以向兩邊擴張;

        (c)的情況,p[i]=ct+p[ct]-i,而且不可能再長。還是利用反證法,如果p[i]兩邊可以再擴張一個字符,如圖中的y和z部分相等,那麼必然x和y部分也相等(根據對稱性),而x和w也相等,從而w和z相等,這樣說明原本的p[ct]可以再增長,而p[ct]已經確定了,所以假設不成立。

圖5

 

        2)i>=ct+p[ct],那麼前面的一些計算信息無法爲我們所用,於是p[i] = 1;

圖6

 

        綜合上述情況,可以得到p[i]的初始值如下:

if (i < ct + p[ct]) {
    p[i] = min(p[2*ct-i], ct + p[ct]-i);
}else {
    p[i] = 1;
}

 

        然後再循環判斷第i-p[i]個字符和第i+p[i]個字符是否相等,如果相等則p[i]++;否則退出循環。然後利用新的i+p[i]更新ct。

 

代碼實現

int Manacher(char *str) {
	int ct = 0, r = 0, maxLen = 1;
	p[0] = 1;
	for(int i = 1; str[i]; ++i) {
		// 1.計算p[i]初始值 
		if(i < r) {
			p[i] = Min(p[2*ct-i], r-i);
		}else {
			p[i] = 1;
		}
		// 2.擴張p[i],以適應達到p[i]最大值 
		while(i-p[i]>=0 && str[i-p[i]] == str[i+p[i]])
			++p[i];
		// 3.更新ct
		if(p[i] + i > r) {
			ct = i;
			r = p[i] + i;
		}
		// 4.更新最長迴文 
		if(2*p[i]-1 > maxLen) {
			maxLen = 2*p[i] - 1;
		}
	}
	return maxLen;
}

     當所有字符都相等時,該算法達到最壞複雜度,觀察可得複雜度O(n)。

 

奇偶迴文轉換

        以上算法只支持字符串長度爲奇數的情況,那麼接下來我們通過一種方法,將偶數字符串轉換成奇數。如圖所示,相鄰字符間插入一個不在該字符串的字符集中的字符,比如$。原串長度爲s,則轉換後的串長度爲2*s+1,所以必然爲奇數。

圖7

 

        並且這麼做以後有幾個比較明顯的性質:

1、任何一個迴文子串的兩端必然是'$'。

2、以'$'爲中心的最長迴文子串的半徑一定是奇數;反之,一定是偶數。

 

 

HDU 3068 最長迴文

Manacher

 

HDU 3294 Girl's research

Manacher

 

HDU 4513 吉哥系列故事——完美隊形II

Manacher+單調性

 

HDU 5677 ztr loves substring

Manacher+動態規劃

 

HDU 5340 Three Palindromes

Manacher+枚舉

 

HDU 5785 Interesting

Manacher+樹狀數組

 

HDU 5371 Hotaru's problem

Manacher+RMQ+枚舉

 

 

HDU 3068 最長迴文

       題意:給定一個長度爲N(N<=110000)的字符串,求它的最長迴文子串的長度。

題解:間隔插入字符'$'後,利用Manacher算法求出每個字符爲中心的最長迴文子串半徑p[i],比較取得最大的p[i]記爲Max,則Max-1就是最後的答案。

 

HDU 3294 Girl's research

       題意:在一個長度爲N(N<=200000)的字符串中,找到一個最長迴文子串,並且輸出。

題解:間隔插入字符'$'後,利用Manacher算法求出每個字符爲中心的最長迴文子串半徑p[i],比較取得最大的p[i],輸出兩端即可。

 

HDU 4513 吉哥系列故事——完美隊形II

       題意:在一個長度爲N(N<=100000)的整數序列中,找到一個連續的迴文序列,且從左到中間保持單調不降。

題解:對於原序列,計算一個輔助數組h,h[i]代表從自己到最左邊的單調不增的序列長度。然後在原序列間隔插入-1,利用Manacher算法求出每個數爲中心的最長迴文子序列的半徑p[i],然後對新序列(插入-1的序列)的每個i (i 下標從0開始計) 進行計算求最大值。迴文和單調取交集,即滿足半徑小的那個。

以第i個元素爲中心軸,如果這個元素是-1,則兩邊必然偶對稱,min{p[i]-1, 2*h[i/2-1]};否則爲奇對稱,取值爲min{p[i]-1, 2*q[i/2]-1},取所有這些串中的最大值就是答案。

圖8

 

 

HDU 5677 ztr loves substring

       題意:給定N(N<=100)個字符串,要求取出其中K(K<=100)個子串,組成一個長度爲L(L <=100)的子串。並且這些子串需要滿足的條件是必須迴文。可行輸出Yes,不可行輸出No。

      題解:利用Manacher求出每個串的迴文子串中長度爲 i (i <= 該字符串長度)的個數。然後將這些個數合併。

      用dp[i][j][k]表示可以選擇的子串長度爲1-i的情況下,選擇個數爲j個,組成長度爲k的方案是否可行。然後就是一個O(n)的狀態轉移,算法最壞複雜度O(n^4)。實際上肯定達不到,因爲只要有一個i滿足dp[i][K][L]=true,則可以直接輸出Yes了。

      初始狀態dp[0][0][0]=true,其它均爲false,然後枚舉進行狀態轉移即可。

 

HDU 5340 Three Palindromes 

       題意:給定N(N<=20000)個字符串,求判斷是否能夠將它切兩刀,將它變成三個迴文串。

      題解:首先還是插入字符'$',求一遍Manacher,然後開始找一些有趣的性質。我們可以分成兩種情況討論:

      1)第一種,中間那個串的最長迴文子串去掉後,兩邊的串分別正好是迴文串。這種情況比較簡單,枚舉每個字符作爲中心軸,去掉中間那個迴文串後,確定兩邊兩個串的中心軸,利用p[i]判斷是否滿足迴文條件即可。

圖9

 

      2)第二種,中間那個串的最長迴文去掉後,兩邊無法再構成迴文;但是中間串縮短長度後,兩邊有可能構成迴文。如圖所示:

圖10

 

這種情況下,我們需要枚舉中間那個串的實際長度,這個實際長度最大值爲p[i],然後每次減2。由於逐漸減小長度的過程中,必然兩個邊界是按照中心軸對稱的,而邊界需要滿足分別和原串的兩邊界對稱,所以在減小長度的同時可以比較原串的收尾是否有不相等的情況,一旦不相等就可以退出循環了,複雜度小於O(n^2)。

 

HDU 5785 Interesting(推薦)

       題意:給定一個長度小於等於N(N<=10^6)的字符串S[N],有三元組(i,j,k)滿足1<=i<=j<k<len,且S[i...j]和S[j+1...k]均爲迴文串。現在需要輸出所有這些三元組的i*k的和模1000000007。

       題解:首先插入字符'$',原串S變成S',然後求一遍Manacher,記錄下p[i]。然後就是枚舉j,看左邊有多少i滿足S[i...j]是迴文,右邊有多少k滿足S[j+1...k]是迴文。如果用樸素的做法,統計所有i的和以及k的和相乘加和,總的複雜度爲O(n^2)。所以我們可以採用樹狀數組來加速這部分求和。

      我們先討論S[i...j]部分的求和統計。目的是找到以j結尾的所有迴文串S[i...j]中i的和。

      首先枚舉所有非'$'的字符y,然後我們的目的是要找到所有的x<=y,滿足g=(x+y)/2,且g+p[g]>y。由於y對應的字符不是'$',根據對稱性,x對應的字符也不是'$'。x字符對應原字符串的下標爲(x+1)/2。

      所以問題轉化成求:

圖11

 

圖12

 

       例如,這個串以第9個字符結尾的迴文串的左端點x集合是{1,5,9},經過座標轉換後,原串的所有i求和爲sum{ (x+1)/2 | x=1,5,9 }=1+3+5=9。

圖13

 

      然而,實際計算的時候,所有i的求和只能通過O(n)的遍歷,這樣總的複雜度會變成O(n^2),爲了能夠用樹狀數組進行統計,我們引入了一箇中間變量,即所有這些迴文串中心g。

      因爲g=(x+y)/2,所以x = 2g-y,於是有:

圖14

 

      sum{g}代表所有滿足條件的迴文的中心軸數值的和,cnt代表有多少個迴文串。那麼,我們可以用兩個樹狀數組分別統計這兩個值,如圖所示:

圖15

 

      維護兩個樹狀數組,一個統計cnt,一個統計sum。計算cnt的時候,對於每個p[g],在g位置+1,g+p[g]位置-1;計算sum的時候,對於每個p[g],在g位置+g,在g+p[g]位置-g。

      這樣對於每個j,就能計算所有i的和了,用同樣方法計算所有k的和,然後相乘取模即可。算法複雜度O(nlogn)。當然還有一個更加高效的算法,每次插入和計算求和的時候都是遞增的進行,所以可以不需要樹狀數組,直接一個O(n)的數組維護前綴和即可,總算法複雜度可以達到O(n)。

 

HDU 5371 Hotaru's problem

       題意:給定一個N(N<=10^5)個元素的數組。定義N-序列爲3K個元素,前K個和中間K個完全對稱,並且前K個和最後K個元素完全相等。求N個元素中最長的N-序列的長度。

      題解:由題意得,前K個和中間K個構成偶迴文,且中間K個和最後K個也構成偶迴文。

首先,數據量大的情況下,用G++提交利用輸入外掛,可以節省500MS時間。然後就是各種亂搞,在相鄰元素中間插入-1後跑一邊Manacher,然後就是枚舉以每個-1爲對稱軸的迴文串,記錄最優解Max。

圖16

 

枚舉每個-1的對稱軸i,然後在p[i]覆蓋範圍內找右邊串的對稱軸j,算法複雜度O(n^2)。但是,這裏可以做很多優化,比如在當前可能的軸i和軸j之間如果沒有一個k滿足k-p[k]+1<=i,則不需要枚舉j直接跳出循環。這一步可以用RMQ來完成。如果當前情況下能夠求得的最優解已經小於等於Max,也不需要繼續往下枚舉。

還有一個比較依賴數據的剪枝,就是3*(Max+1)>N的時候退出整個循環(用於所有元素均相等的情況)。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章