字符串-Manacher算法(你知道马拉车算法吗?)
文章目錄
- 原理
- 奇偶問題
- p[]數(shù)組
- 馬拉車求p[]
- 模板
- 例題
- P3805 【模板】manacher算法
- P1659 拉拉隊排練
原理
馬拉車算法當(dāng)然不是馬拉著車的奇奇怪怪的東西,是Manacher’s Algorithm的音譯。
Manacher’s Algorithm馬拉車算法是一種可以在O(n)O(n)O(n)線性時間內(nèi)求最長回文子串的算法(Manacher是人名,發(fā)明者)。所謂的回文也就是正讀反讀都是一樣的,比如noonnoonnoon、levellevellevel,那在一個字符串中找最長的回文子串,一般使用中心擴展法,也就是枚舉每一個字符為對稱中心,然后向左右延伸并判斷是否相等,但這種方法的時間復(fù)雜度是O(n2)O(n^2)O(n2)。
分析中心擴展法效率差的原因,是因為它做了大量重復(fù)計算,比如字符串aaaaaaaaaaaaaaaaaaaaaaaaaaa、abababaabababaabababa,也就是最壞的情況——各個回文子串相互重疊。而馬拉車算法則通過一些空間記錄和回文對稱的特點來避免這種重復(fù)計算。
奇偶問題
首先需要解決字符串長度奇偶時,對稱中心不一致的問題。如串abaabaaba和abbaabbaabba都是回文串,但是它們的對稱中心分別是不一致的,前者是bbb,后者是位于bbb中間。
我們在每個字符前后加上一個’#‘符,并且為了后續(xù)不越界和求原下標(biāo)方便,我們讓新字符串ttt的下標(biāo)從1開始,即下標(biāo)0處也賦’#’。這樣處理后字符串的長度就都是奇數(shù)了,對稱中心自然也都是二分之一處的字符,不會再是位于兩字符中間的情況。
p[]數(shù)組
現(xiàn)在我們引用一個數(shù)組p[]來表示中心擴展的最大長度,而它就是去掉’#'后原回文子串的長度,也就是我們求的答案。
以字符串ababaabababaabababaab為例,通過PPP的下標(biāo)iii減去P[i]P[i]P[i]再除以222就能求出原回文子串,如下標(biāo)888處P[8]=3P[8]=3P[8]=3,(8?3)/2=2(8-3)/2=2(8?3)/2=2,即在原串SSS中下標(biāo)222開始長度為333的子串是一個回文子串(abaabaaba)。
馬拉車求p[]
前面都是準(zhǔn)備工作,下面才是馬拉車的核心算法,它充分利用了回文的對稱性,比如說對于回文串ababaababaababa,不難發(fā)現(xiàn)它的p[]p[]p[]數(shù)組也是關(guān)于中心對稱的,如下圖:
但這只限于回文串中可以這樣直接鏡像,還需要推導(dǎo)至一般情況,仍以串ababaabababaabababaab為例。
設(shè)midmidmid為對稱中心,rrr表示回文串的右邊半徑(r=mid+p[i]r=mid+p[i]r=mid+p[i]),下標(biāo)iii即當(dāng)前要求的p[i]p[i]p[i],下標(biāo)jjj表示iii關(guān)于中心midmidmid鏡像對稱的下標(biāo),分如下三種情況:
合法范圍內(nèi)
就是上面討論的情形,直接賦值即可。
jjj遇到原字符串左邊界
此時不能直接賦值,因為jjj位置處是因為向左到盡頭越界,而鏡像處的iii向左沒有越界,應(yīng)該繼續(xù)比較,即用中心擴展法。
雖然此處答案都是1,但jjj是因為到了首字符‘#’必不相等,而iii是可以匹配的,只是此處aaa和bbb匹配失敗。
iii>=rrr
同上面一樣不能利用對稱性了,將p[i]p[i]p[i]置0,用中心擴展法。
同時注意更新midmidmid和rrr,也就是當(dāng)更新p[i]p[i]p[i]后,求出新的r(p[i]+i)r(p[i]+i)r(p[i]+i),若大于舊的rrr,就更新midmidmid和rrr,即設(shè)置當(dāng)前點iii為新的對稱中心。
(插播反爬信息 )博主CSDN地址:https://wzlodq.blog.csdn.net/
模板
#include <iostream> #include <string> using namespace std; const int maxn = 1000006; int p[maxn]; string s, t; int main() {cin >> s;int n = s.size();t = "#";for (int i = 0; i < n; i++) {t += '#';t += s[i];}t += '#';int m = t.size();int mid = 0, r = 0;int len = 0, st = 0;//回文串長度和起始位置for (int i = 1; i < m - 1; i++) {int j = mid * 2 - i;if (i < r) //右邊半徑內(nèi)p[i] = min(r - i, p[j]);//防止超出relse p[i] = 0; //>=r情況while (t[i + 1 + p[i]] == t[i - 1 - p[i]])p[i]++; //中心擴展if (i + p[i] > r) { //更新rmid = i;r = i + p[i];}if (p[i] > len) { //順便更新答案len = p[i];st = (i - p[i]) / 2;}}cout << len << "\n" << s.substr(st, len);return 0; }注意時間復(fù)雜度是O(n)O(n)O(n),當(dāng)里面的while循環(huán)中心擴展后,下次循環(huán)是不會再訪問到這些節(jié)點的,會直接用對稱性。
例題
P3805 【模板】manacher算法
洛谷P3805 【模板】manacher算法
題目描述
給出一個只由小寫英文字符 \texttt a,\texttt b,\texttt c,\ldots\texttt y,\texttt za,b,c,…y,z 組成的字符串 SS ,求 SS 中最長回文串的長度 。
字符串長度為 nn。
輸入格式
一行小寫英文字符 \texttt a,\texttt b,\texttt c,\ldots\texttt y,\texttt za,b,c,…y,z 組成的字符串 SS。
輸出格式
一個整數(shù)表示答案。
輸入輸出樣例
輸入 #1
aaa
輸出 #1
3
說明/提示
1≤n≤1.1×107
P1659 拉拉隊排練
洛谷P1659 [國家集訓(xùn)隊]拉拉隊排練
題目描述
艾利斯頓商學(xué)院籃球隊要參加一年一度的市籃球比賽了。拉拉隊是籃球比賽的一個看點,好的拉拉隊往往能幫助球隊增加士氣,贏得最終的比賽。所以作為拉拉隊隊長的楚雨蕁同學(xué)知道,幫助籃球隊訓(xùn)練好拉拉隊有多么的重要。
拉拉隊的選拔工作已經(jīng)結(jié)束,在雨蕁和校長的挑選下,n位集優(yōu)秀的身材、舞技于一體的美女從眾多報名的女生中脫穎而出。這些女生將隨著籃球隊的小伙子們一起,和對手抗衡,為艾利斯頓籃球隊加油助威。
一個陽光明媚的早晨,雨蕁帶領(lǐng)拉拉隊的隊員們開始了排練。n個女生從左到右排成一行,每個人手中都舉了一個寫有26個小寫字母中的某一個的牌子,在比賽的時候揮舞,為小伙子們吶喊、加油。
雨蕁發(fā)現(xiàn),如果連續(xù)的一段女生,有奇數(shù)個,并且他們手中的牌子所寫的字母,從左到右和從右到左讀起來一樣,那么這一段女生就被稱作和諧小群體。
現(xiàn)在雨蕁想找出所有和諧小群體,并且按照女生的個數(shù)降序排序之后,前K個和諧小群體的女生個數(shù)的乘積是多少。由于答案可能很大,雨蕁只要你告訴她,答案除以19930726的余數(shù)是多少就行了。
輸入格式
輸入為標(biāo)準(zhǔn)輸入。
第一行為兩個正整數(shù)n和K,代表的東西在題目描述中已經(jīng)敘述。
接下來一行為n個字符,代表從左到右女生拿的牌子上寫的字母。
輸出格式
輸出為標(biāo)準(zhǔn)輸出。
輸出一個整數(shù),代表題目描述中所寫的乘積除以19930726的余數(shù),如果總的和諧小群體個數(shù)小于K,輸出一個整數(shù)-1。
輸入輸出樣例
輸入 #1
5 3
ababa
輸出 #1
45
說明/提示
樣例說明
和諧小群體女生所拿牌子上寫的字母從左到右按照女生個數(shù)降序排序后為ababa, aba, aba, bab, a, a, a, b, b,前三個長度的乘積為 。
跑一遍馬拉車,并使用cnt[]數(shù)組記錄回文串長度對應(yīng)數(shù)量,過濾奇數(shù)的,從大往小循環(huán)即可,另外注意直接乘會超時,用快速冪。
#include <iostream> #include <string> using namespace std; const int maxn = 2000006; const int mod = 19930726; typedef long long ll; int p[maxn], cnt[maxn]; string s, t; ll n, k; ll qpow(ll x, ll y) {ll res = 1;while (y) {if (y & 1)res = (res * x) % mod;x = (x * x) % mod;y >>= 1;}return res; } int main() {cin >> n >> k >> s;t = "#";for (int i = 0; i < n; i++) {t += '#';t += s[i];}t += '#';ll m = t.size();int mid = 0, r = 0;for (int i = 1; i < m - 1; i++) {int j = mid * 2 - i;if (i < r)p[i] = min(r - i, p[j]);else p[i] = 0; while (t[i + 1 + p[i]] == t[i - 1 - p[i]])p[i]++; if (i + p[i] > r) { mid = i;r = i + p[i];}if (p[i] % 2)cnt[p[i]]++;}ll sum = 0, ans = 1;for (int i = n; i >= 1; i--) {if (i % 2 == 0)continue;sum += cnt[i];ll cur = min(sum, k);k -= sum;ans = (ans * qpow(i, cur)) % mod;if (k < 0)break;}if (k > 0)ans = -1;cout << ans;return 0; }原創(chuàng)不易,請勿轉(zhuǎn)載(本不富裕的訪問量雪上加霜 )
博主首頁:https://wzlodq.blog.csdn.net/
來都來了,不評論兩句嗎👀
如果文章對你有幫助,記得一鍵三連?
總結(jié)
以上是生活随笔為你收集整理的字符串-Manacher算法(你知道马拉车算法吗?)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HDU 3507 斜率优化入学习
- 下一篇: 重庆大学计算机专业排名全国多少名,重庆大