1. 程式人生 > 實用技巧 >[BZOJ3790]神奇項鍊(manacher)

[BZOJ3790]神奇項鍊(manacher)

題目

Description

母親節就要到了,小 H 準備送給她一個特殊的項鍊。這個項鍊可以看作一個用小寫字母組成的字串,每個小寫字母表示一種顏色。為了製作這個項鍊,小 H 購買了兩個機器。第一個機器可以生成所有形式的迴文串,第二個機器可以把兩個迴文串連線起來,而且第二個機器還有一個特殊的性質:假如一個字串的字尾和一個字串的字首是完全相同的,那麼可以將這個重複部分重疊。例如:aba和aca連線起來,可以生成串abaaca或 abaca。現在給出目標項鍊的樣式,詢問你需要使用第二個機器多少次才能生成這個特殊的項鍊。

Input

輸入資料有多行,每行一個字串,表示目標項鍊的樣式。
每個測試資料,輸入不超過 5行


每行的字串長度小於等於 50000

Output

多行,每行一個答案表示最少需要使用第二個機器的次數。

Sample Input

abcdcba 
abacada 
abcdef 

Sample Output

0
2
5

思路:

首先你必須知道基礎的manacher演算法;作者(部落格園:木偶人-怪咖)懶得寫

那麼這一題怎麼寫呢;

題目可以轉化為多少個迴文串可以覆蓋全部;

很容易想到先把每個迴文串的最左節點和最右節點求出來;

然後把每個迴文串按左節點排序,如果左節點一樣,最好按右節點大的排在前面;

這樣就方便找覆蓋最大的迴文串,並且全部覆蓋;作者部落格園:木偶人-怪咖(防偽)

然後再貪心,第一次加入左節點為1並且右節點延伸最遠的迴文串;

之後加入的數,我們需要中間不留空隙,所以加入的迴文串需要與之前的迴文串重合(為什麼重合,看題!!)或相鄰,且向右延伸最遠;

作者部落格園:木偶人-怪咖(防偽)

如題目樣例的第二個:

abacada

第一次加入aba;左節點為1且向右延伸最遠的串;

第二次加入aca;與前面串重合且向右延伸最遠的串;

第三次加入ada;....................................................作者部落格園:木偶人-怪咖(防偽)

根據題意需要連線兩次,所以答案是2;


程式碼:

#include<bits/stdc++.h>
typedef 
long long ll; using namespace std; inline ll read() { ll a=0,f=1; char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();} while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();} return a*f; } char s[1000010],snew[2000010]; ll n,p[2000010]; struct ljq { ll L,R; }f[4000010]; inline ll build() { snew[0]='$';snew[1]='#'; ll j=2,len=strlen(s); for(ll i=0;i<len;i++) { snew[j]=s[i];j++; snew[j]='#'; j++;//* } snew[j]='\0';//防越界標誌 n=j-1;//n是修改過後字串的長度 return j; } inline void manacher() { ll len=build(); ll id=0,mx=0; for(ll i=1;i<len;i++)//為什麼是i<len而不是i<=len呢; //在build函式中j在賦值之後 又加了一次 如 * { ll j=2*id-i; if(i<mx) p[i]=min(p[j],mx-i); else p[i]=1; while(snew[i+p[i]]==snew[i-p[i]]) p[i]++; if(p[i]+i>mx) { id=i; mx=p[i]+i; }//上面為manacher的基本sao操作; f[i].L=i-(p[i]-1); f[i].R=i+(p[i]-1);//記錄好每一個迴文串的端點; } } inline ll cmp(ljq a,ljq b) { if(a.L==b.L) return a.R>b.R; else return a.L<b.L; }//排序,為什麼這樣排,看思路 int main() { while(scanf("%s",s)!=EOF) { memset(p,0,sizeof(p)); memset(f,0,sizeof(f)); manacher(); sort(f+1,f+n+1,cmp); // cout<<endl; // for(ll i=1;i<=n;i++) // cout<<f[i].L<<" "<<f[i].R<<endl; if(f[1].L==1&&f[1].R==n) { printf("0\n"); continue; }//因為排序按覆蓋最長的排序,所以f[1]代表整個迴文的最長覆蓋; //如果整個字元就是迴文串,那麼就不需要連線了,直接輸出0; ll ans=0; ll i=1,pre=0; while(i<=n) { ll sum=0;//pre表示當前加入的最後一個迴文串的右端點 ; while(f[i].L-1<=pre)//為了防止中間不留空隙,每個迴文必須相連 ; { sum=max(sum,f[i].R);//找到延伸最遠的迴文串; i++; } ans++; pre=sum;//記下右端點 if(pre==n)//如果全部覆蓋了,那麼退出 break; } printf("%lld\n",ans-1);//因為第一次加回文的時候,ans++了,而題目是求相連的次數,所以要減一 } return 0; }

作者部落格園:木偶人-怪咖(防偽)