HDU - 3068 最長回文(馬拉車算法模板題)

HDU原題鏈接:傳送門

最長回文

Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 33823 Accepted Submission(s): 12368

Problem Description

給出一個(gè)只由小寫英文字符a,b,c...y,z組成的字符串S,求S中最長回文串的長度.
回文就是正反讀都是一樣的字符串,如aba, abba等

Input

輸入有多組case,不超過120組,每組輸入為一行小寫英文字符a,b,c...y,z組成的字符串S
兩組case之間由空行隔開(該空行不用處理)
字符串長度len <= 110000

Output

每一行一個(gè)整數(shù)x,對應(yīng)一組case,表示該組case的字符串中所包含的最長回文長度.

Sample Input

aaaa

abab

Sample Output

4
3

題目分析——馬拉車算法

本題是Manacher(馬拉車)算法的一題模板題敷存,解決一個(gè)字符串的最長回文子串的問題尿贫,解題之前需要講述一下馬拉車算法的原理吉懊,我找了一篇講解這個(gè)算法比較好的博客:傳送門 參考前輩的文章后缩膝,在此也做出一些自己的總結(jié)鲸伴,以免時(shí)間久了自己產(chǎn)生遺忘冗疮,當(dāng)然馬拉車算法代碼量極少矾兜,但是其思想?yún)s需要仔細(xì)揣摩,做好準(zhǔn)備我們就開始吧巩检!

首先對于一個(gè)給出的字符串厚骗,例如aba,abba兢哭,abababadfsaa等领舰,最直觀的想到的去求它的最長回文子串的方法是從左到右遍歷每一個(gè)字符,以這個(gè)字符作為回文串的中心,然后不斷向兩邊擴(kuò)充提揍,直到不滿足后遍歷下一個(gè)字符,求出最長的回文子串的長度煮仇。但是這么做在字符串長度很長時(shí)效率很低劳跃,花費(fèi)大量的時(shí)間。

在介紹馬拉車算法之前浙垫,有必要先提及一個(gè)很巧妙且重要的方法刨仑,不知你是否發(fā)現(xiàn),通過上述的方法求回文子串夹姥,我們總是以一個(gè)字符為出發(fā)點(diǎn)向兩邊匹配杉武,那么每次所匹配出來的回文串都是奇數(shù),而無法判斷abba這樣的偶數(shù)回文串辙售,所以就產(chǎn)生了一種拼接字符串的神奇方法轻抱,將一個(gè)初始的字符串用一個(gè)不會(huì)出現(xiàn)的字符隔開,例如#a#b#a#旦部,#a#b#b#a#祈搜,你驚奇的發(fā)現(xiàn),無論是奇數(shù)還是偶數(shù)的字符串經(jīng)過這么處理之后不但不會(huì)改變它原有的對稱特性(只是長度好像變長了一倍士八?因?yàn)?也是一個(gè)字符容燕,且它的存在不會(huì)打斷原有的回文特性,只是增長了而已婚度,放心下面會(huì)講明它變長的長度是有著一個(gè)固定的數(shù)學(xué)關(guān)系的)蘸秘,而且所有的串都變成了奇數(shù)串(奇+偶=奇,偶+奇=奇)蝗茁,秒按茁病!

那么我們得到了一個(gè)類似#a#b#a#的串之后該怎么做呢哮翘,別急灰粮,我們需要將這個(gè)字符再做一點(diǎn)處理,在它最前面插入一個(gè)不會(huì)出現(xiàn)且不同于#的字符忍坷,就像$#a#b#a#這樣粘舟,這么做有一個(gè)好處,就是我們將實(shí)際會(huì)用于算法求解的字符從下標(biāo)0開始轉(zhuǎn)化成了從下標(biāo)1開始佩研,后面我們遍歷時(shí)也是從下標(biāo)1開始的柑肴,這樣第一個(gè)有效字符的位置就是s[1],第二個(gè)就是s[2]旬薯,方便后面我們使用馬拉車算法進(jìn)行推導(dǎo)晰骑。

對于一個(gè)字符串如:$#a#a#b#a#a#,我們設(shè)P[i]為以第i個(gè)字符為回文串中心時(shí)最長的回文半徑(包含S[i]本身),那么遍歷P[i]就可以找出最長的回文子串的長度

下標(biāo) 0 1 2 3 4 5 6 7 8 9 10 11
S $ # a # a # b # a # a #
P 1 1 2 3 2 1 6 1 2 3 2 1
下標(biāo) 0 1 2 3 4
s a a b a a
p 1 1 3 1 1

此時(shí)就會(huì)有人提出疑問硕舆,這個(gè)是我拼接之后的字符串呀秽荞,求出它的最長回文子串的長度與求拼接前的最長回文子串長度有什么關(guān)系嗎?有關(guān)系抚官,你發(fā)現(xiàn)上述拼接后的字符串最長回文子串是以i==6為中心的扬跋,就是以b這個(gè)字符為中心,那么你驚奇的發(fā)現(xiàn)原字符串最長回文子串一定也是以b為中心的凌节,因?yàn)槲覀兩厦婢吞岬搅饲仗唇拥?不會(huì)影響原字符串的回文特性,只是讓它們在原先的基礎(chǔ)上都變長了而已倍奢,而它們之間的關(guān)系很顯然是2倍關(guān)系P[i]==2*p[i]朴上,由于題目所求的是原字符串的最長回文子串的長度,那么答案就是P[i]-1卒煞,因?yàn)槲覀冏允甲越K用拼接后的字符串參與推導(dǎo)痪宰,所以只要關(guān)注P[i]數(shù)組即可。

那么所有問題的矛頭都指向一處畔裕!p[i]怎么求(下面都用小寫的p代表拼接后字符串的以i為回文中心的最長回文半徑酵镜,頻繁切換大寫讓我覺得有些異樣w(?Д?)w),馬拉車算法柴钻,啟動(dòng)淮韭!因?yàn)槲覀兪菑淖蟮接冶闅vi的,在計(jì)算p[i]時(shí)我們要確保p[1]~p[i-1]已經(jīng)求出贴届,這樣才能借助回文串的對稱特性靠粪,也是就前面求出來的p[j]減少后面p[i]的匹配次數(shù)(此時(shí)你可能不明白這句話的含義,且仔細(xì)往下看)毫蚓,我們要用到兩個(gè)輔助的變量id和mx占键,id為i之前的一個(gè)能向右伸展的最遠(yuǎn)的回文串的回文串中心,mx為以id為回文中心的這個(gè)回文串往右(往左)能伸展到的最遠(yuǎn)的距離

還是借助一下圖來更加清晰的展示

m1.png

其中j是i關(guān)于id的對稱點(diǎn)元潘,j=2*id-i畔乙,這個(gè)自行推一下便可,那么此時(shí)上圖展示的只是其中一種情況翩概,當(dāng)以i為回文中心的時(shí)i點(diǎn)在左側(cè)一個(gè)向右伸展足夠長至把i點(diǎn)包括其中的回文串內(nèi)牲距,即i<mx,當(dāng)這種情況時(shí)钥庇,又分為兩種小的情況

  1. 當(dāng)以p[j]即p[2*id-i]為中心的回文串長度沒有超過以id為對稱中心的回文串的左邊界
m2.png

由于以i和j為中心的回文串是關(guān)于id對稱的兩個(gè)子串牍鞠,所以p[i]自然而然和p[j]相等,即p[i]=p[2*id-i]

  1. 當(dāng)以p[j]為中心的回文串左邊界超過以id為回文中心的回文串的左邊界
m3.png

左邊的綠色線代表超過id左邊界的回文串评姨,此時(shí)难述,由于對稱關(guān)系,只能得知以i為中心的回文串在id右邊界之內(nèi)的部分和以j為中心的回文串在id左邊界之內(nèi)的部分是對稱的,而以i為中心的超過右邊界的部分則只能老老實(shí)實(shí)向兩側(cè)匹配胁后,所以在這兩種情況下店读,如果p[2 * id-i]>=mx-i+1,則說明超過左邊界攀芯,則只取用邊界內(nèi)的對稱部分p[i]=mx-i+1屯断,如果p[2 * id-i]<mx-i+1,說明不超過左邊界敲才,則由于p[2 * id-i]和p[i]對稱裹纳,直接繼承择葡,p[i]=p[2 * id-i]紧武。

//在借助回文串i前已經(jīng)計(jì)算出的數(shù)據(jù)減少匹配次數(shù)后,依舊要老老實(shí)實(shí)調(diào)用下面的函數(shù)去匹配
while(i-p[i]>=1&&i+p[i]<=len*2+1){    //下標(biāo)向左不越界敏储,下標(biāo)向右不越界
    if(s[i-p[i]]==s[i+p[i]]) p[i]++; //初始化p[i]都是1,至少回文半徑是1
    else break;
} 

不要忘記還有一種大情況阻星,就是i點(diǎn)在mx的邊界或者外面時(shí)

m4.png
m5.png

在這兩種情況下,p[i]是否對稱都要從超過id右邊界(mx)的地方開始一個(gè)一個(gè)首尾比較已添,所以沒有辦法借助p[j]來減少預(yù)匹配的次數(shù)妥箕,直接調(diào)用上面的函數(shù)老老實(shí)實(shí)匹配吧

要注意的是隨著每一次i的遍歷之后,如果mx能更加向右延伸更舞,即p[i]+i-1>p[id]+id-1畦幢,-1是因?yàn)橹貜?fù)加點(diǎn)了,仔細(xì)推理一下便可缆蝉,例如id=5宇葱,p[id]=3,則mx=p[id]+id-1=7刊头,那么要更新id的位置為i黍瞧,更新mx為p[i]+i-1,確保id永遠(yuǎn)是右邊界最接近下一個(gè)要遍歷的i點(diǎn)那個(gè)對稱中心

示例代碼

#include<iostream>
#include<string>
#include<cstring>
#include<string.h>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;

const int N=110000+10;
char s[N*2];
int p[N*2];

int main(){
    while(scanf("%s",s)!=EOF){
        int len=strlen(s);
        for(int i=len;i>=1;i--){
            s[i*2+1]='#';
            s[i*2]=s[i-1];
//          s[i*2-1]='#';   具體的拼接字符串的方法每個(gè)人寫的都有些細(xì)微的差別 
        }
        s[1]='#';
        s[0]='$';
//      printf("%s\n",s);
//      memset(p,0,sizeof(p));
        int maxx=1;         //用于統(tǒng)計(jì)最后的輸出結(jié)果 
        fill(p,p+2*N,1);
        int id=1,mx=1;      //mx=id+p[id]-1==1 代表i前的以id為中心的回文串可以達(dá)到的最右邊的邊界 
        for(int i=2;i<=len*2+1;i++){
            if(i<mx){
                p[i]=min(p[2*id-i],mx-i+1); //i關(guān)于id的對稱點(diǎn)2*id-i原杂,回文串半徑可能超過mx-i+1印颤,有可能更短 
            }
            //上述if語句借助了回文串的對稱特性減少匹配次數(shù)
            //此時(shí)如果p[2*id-i]>=mx-i+1,則相對的i的回文串半徑也可能更長
            //或者i本身就在mx的外面穿肄,則無法借助上述if語句減少以i為中心的回文串的匹配次數(shù)
            while(i-p[i]>=1&&i+p[i]<=len*2+1){
                if(s[i-p[i]]==s[i+p[i]]) p[i]++;
                else break;
            } 
            //需要i每往后一次就要實(shí)時(shí)更新id和mx的大小
            if(p[i]+i-1>mx){
                mx=p[i]+i-1;
                id=i; 
            } 
            if(p[i]>maxx) maxx=p[i];
        }
        printf("%d\n",maxx-1);
    }
    return 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末年局,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子咸产,更是在濱河造成了極大的恐慌某宪,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锐朴,死亡現(xiàn)場離奇詭異兴喂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門衣迷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來畏鼓,“玉大人,你說我怎么就攤上這事壶谒≡平茫” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵汗菜,是天一觀的道長让禀。 經(jīng)常有香客問我,道長陨界,這世上最難降的妖魔是什么巡揍? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮菌瘪,結(jié)果婚禮上腮敌,老公的妹妹穿的比我還像新娘。我一直安慰自己俏扩,他們只是感情好糜工,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著录淡,像睡著了一般捌木。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嫉戚,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天刨裆,我揣著相機(jī)與錄音,去河邊找鬼彼水。 笑死崔拥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凤覆。 我是一名探鬼主播链瓦,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盯桦!你這毒婦竟也來了慈俯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤拥峦,失蹤者是張志新(化名)和其女友劉穎贴膘,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體略号,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刑峡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年洋闽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片突梦。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诫舅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宫患,到底是詐尸還是另有隱情刊懈,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布娃闲,位于F島的核電站虚汛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏皇帮。R本人自食惡果不足惜卷哩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望玲献。 院中可真熱鬧殉疼,春花似錦梯浪、人聲如沸捌年。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽礼预。三九已至,卻和暖如春虏劲,著一層夾襖步出監(jiān)牢的瞬間托酸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工柒巫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留励堡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓堡掏,卻偏偏與公主長得像应结,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子泉唁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容