这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 | |||
2020-2021:teams:legal_string:lgwza:manacher_算法 [2020/10/02 14:08] lgwza |
2020-2021:teams:legal_string:lgwza:manacher_算法 [2020/10/02 14:12] (当前版本) lgwza |
||
---|---|---|---|
行 137: | 行 137: | ||
</hidden> | </hidden> | ||
+ | ==== 统一处理 ==== | ||
+ | |||
+ | 虽然在讲解过程中及上述实现中我们将 $d_1[]$ 和 $d_2[]$ 的计算分开考虑,但实际上可以通过一个技巧将二者的计算统一为 $d_1[]$ 的计算。 | ||
+ | |||
+ | 给定一个长度为 $n$ 的字符串 $s$,我们在其 $n+1$ 个空中插入分隔符 $\#$,从而构造一个长度为 $2n+1$ 的字符串 $s'$。举例来说,对于字符串 $s=\mathtt{abababc}$,其对应的 $s'=\mathtt{\#a\#b\#a\#b\#a\#b\#c}$。 | ||
+ | |||
+ | 对于字母间的 $\#$,其实际意义为 $s$ 中对应的“空”。而两端的 $\#$ 则是为了实现的方便。 | ||
+ | |||
+ | 注意到,在对 $s'$ 计算 $d_1[]$ 后,对于一个位置 $i$,$d_1[i]$ 所描述的最长的子回文串必定以 $\#$ 结尾(若以字母结尾,由于字母两侧必定各有一个 $\#$,因此可向外扩展一个得到一个更长的)。因此,对于 $s$ 中一个以字母为中心的极大子回文串,设其长度为 $m+1$,则其在 $s'$ 中对应一个以相应字母为中心,长度为 $2m+3$ 的极大子回文串;而对于 $s$ 中一个以空为中心的极大子回文串,设其长度为 $m$,则其在 $s'$ 中对应一个以相应表示空的 $\#$ 为中心,长度为 $2m+1$ 的极大子回文串(上述两种情况下的 $m$ 均为偶数,但该性质成立与否并不影响结论)。综合以上观察及少许计算后易得,在 $s'$ 中,$d_1[i]$ 表示在 $s$ 中以对应位置为中心的极大子回文串的 **总长度加一**。 | ||
+ | |||
+ | 上述结论建立了 $s'$ 的 $d_1[]$ 同 $s$ 的 $d_1[]$ 和 $d_2[]$ 间的关系。 | ||
+ | |||
+ | 由于该统一处理本质上即求 $s'$ 的 $d_1[]$,因此在得到 $s'$ 后,代码同上节计算 $d_1[]$ 的一样。 | ||
+ | |||
+ | ===== 例题 ===== | ||
+ | |||
+ | [[https://www.luogu.com.cn/problem/P3805|P3805 【模板】manacher算法]] | ||
+ | |||
+ | 给出一个只由小写英文字符 $a,b,c,\cdots,y,z$ 组成的字符串 $S$,求 $S$ 中最长回文串的长度。字符串长度为 $n$。 | ||
+ | |||
+ | 代码: | ||
+ | |||
+ | <hidden> | ||
+ | <code cpp> | ||
+ | #include<bits/stdc++.h> | ||
+ | using namespace std; | ||
+ | const int N=1.1e7+5; | ||
+ | char Ma[N<<1]; | ||
+ | int Mp[N<<1]; | ||
+ | void Manacher(char s[],int len){ | ||
+ | int l=0; | ||
+ | Ma[l++]='$'; | ||
+ | Ma[l++]='#'; | ||
+ | for(int i=0;i<len;i++){ | ||
+ | Ma[l++]=s[i]; | ||
+ | Ma[l++]='#'; | ||
+ | } | ||
+ | Ma[l]=0; | ||
+ | int mx=0,id=0; | ||
+ | for(int i=0;i<l;i++){ | ||
+ | Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1; | ||
+ | while(Ma[i+Mp[i]]==Ma[i-Mp[i]]) Mp[i]++; | ||
+ | if(i+Mp[i]>mx){ | ||
+ | mx=i+Mp[i]; | ||
+ | id=i; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | char s[N]; | ||
+ | int main(){ | ||
+ | scanf("%s",s); | ||
+ | int len=strlen(s); | ||
+ | Manacher(s,len); | ||
+ | int ans=0; | ||
+ | for(int i=0;i<2*len+2;i++) | ||
+ | ans=max(ans,Mp[i]-1); | ||
+ | printf("%d",ans); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | |||
+ | |||
+ | [[https://ac.nowcoder.com/acm/contest/7817/A|ABB]] | ||
+ | |||
+ | **题意**:在给定字符串 $s$ 的末尾添加尽可能少的字符,使其成为回文串。 | ||
+ | |||
+ | **题解**:等价于求解原字符串中以末尾字符结尾的最长回文子串。 | ||
+ | |||
+ | <hidden> | ||
+ | <code cpp> | ||
+ | #include<bits/stdc++.h> | ||
+ | using namespace std; | ||
+ | const int N=4e5+5; | ||
+ | char Ma[N<<1]; | ||
+ | int Mp[N<<1]; | ||
+ | void Manacher(char s[],int len){ | ||
+ | int l=0; | ||
+ | Ma[l++]='$'; | ||
+ | Ma[l++]='#'; | ||
+ | for(int i=0;i<len;i++){ | ||
+ | Ma[l++]=s[i]; | ||
+ | Ma[l++]='#'; | ||
+ | } | ||
+ | Ma[l]=0; | ||
+ | int mx=0,id=0; | ||
+ | for(int i=0;i<l;i++){ | ||
+ | Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1; | ||
+ | while(Ma[i+Mp[i]]==Ma[i-Mp[i]]) Mp[i]++; | ||
+ | if(i+Mp[i]>mx){ | ||
+ | mx=i+Mp[i]; | ||
+ | id=i; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | char s[N]; | ||
+ | int main(){ | ||
+ | int n; | ||
+ | scanf("%d",&n); | ||
+ | scanf("%s",s); | ||
+ | int len=n; | ||
+ | Manacher(s,len); | ||
+ | int ans=0; | ||
+ | for(int i=0;i<2*len+2;i++) | ||
+ | if(Mp[i]-1==((2*len+1)-i)) | ||
+ | ans=max(ans,Mp[i]-1); | ||
+ | printf("%d",len-ans); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | |||
+ | [[https://www.luogu.com.cn/problem/P4555|P4555 [国家集训队]最长双回文串]] | ||
+ | |||
+ | **题意**:输入长度为 $n$ 的串 $S$,求 $S$ 的最长双回文子串 $T$,即可将 $T$ 分为两部分 $X$,$Y$,$(|X|,|Y|\ge1)$ 且 $X$ 和 $Y$ 都是回文串。 | ||
+ | |||
+ | **题解**:做 Manacher 时维护以 $i$ 为结尾的最长回文子串的长度 $ll[i]$,和以 $i$ 为开头的最长回文子串的长度 $rr[i]$。 | ||
+ | |||
+ | <hidden> | ||
+ | <code cpp> | ||
+ | #include<bits/stdc++.h> | ||
+ | using namespace std; | ||
+ | const int N=1e5+5; | ||
+ | char Ma[N<<1]; | ||
+ | int Mp[N<<1]; | ||
+ | int ll[N<<1],rr[N<<1]; | ||
+ | void Manacher(char s[],int len){ | ||
+ | int l=0; | ||
+ | Ma[l++]='$'; | ||
+ | Ma[l++]='#'; | ||
+ | for(int i=0;i<len;i++){ | ||
+ | Ma[l++]=s[i]; | ||
+ | Ma[l++]='#'; | ||
+ | } | ||
+ | Ma[l]=0; | ||
+ | int mx=0,id=0; | ||
+ | for(int i=0;i<l;i++){ | ||
+ | Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1; | ||
+ | while(Ma[i+Mp[i]]==Ma[i-Mp[i]]) Mp[i]++; | ||
+ | if(i+Mp[i]>mx){ | ||
+ | mx=i+Mp[i]; | ||
+ | id=i; | ||
+ | } | ||
+ | ll[i+Mp[i]-1]=max(ll[i+Mp[i]-1],Mp[i]-1); | ||
+ | rr[i-Mp[i]+1]=max(rr[i-Mp[i]+1],Mp[i]-1); | ||
+ | } | ||
+ | for(int i=1;i<l;i+=2) rr[i]=max(rr[i],rr[i-2]-2); | ||
+ | for(int i=l-1;i>=1;i-=2) ll[i]=max(ll[i],ll[i+2]-2); | ||
+ | } | ||
+ | char s[N]; | ||
+ | int main(){ | ||
+ | scanf("%s",s); | ||
+ | int len=strlen(s); | ||
+ | Manacher(s,len); | ||
+ | int ans=0; | ||
+ | for(int i=1;i<2*len+2;i+=2) | ||
+ | if(rr[i]&&ll[i]) | ||
+ | ans=max(ans,ll[i]+rr[i]); | ||
+ | printf("%d",ans); | ||
+ | return 0; | ||
+ | } | ||
+ | </code> | ||
+ | </hidden> | ||
+ | |||
+ | |||
+ | |||
+ | ===== 参考链接 ===== | ||
+ | |||
+ | [[https://oi-wiki.org/string/manacher/|OI Wiki]] | ||