Manacher算法讲解||HJ32 密码截取
HJ32 密码截取
题目:https://www.nowcoder.com/practice/3cd4621963e8454594f00199f4536bb1?tpId=37&tqId=21255&rp=1&ru=/exam/oj/ta&qru=/exam/oj/ta&sourceUrl=%2Fexam%2Foj%2Fta%3FtpId%3D37&difficulty=undefined&judgeStatus=undefined&tags=&title=
这个题目范围n才2500,随便什么暴力都能做(谢谢华为,你真善良)。但是我想复习一下Manacher,又想起自己没怎么写过算法讲解的帖子,(而且自己以前写的板子长得很丑陋),就干脆作为manacher模板题,详细讲解一下Manacher算法。
Manacher算法是一种高效的算法,用于在字符串中找到最长的回文子串。它的时间复杂度是O(n),非常适合处理长字符串。
Manacher算法的基本思想
1. 插入特殊字符:
为了处理奇数长度和偶数长度的回文子串,Manacher算法首先在原字符串的每个字符之间插入一个特殊字符(例如`#`),并在字符串开始和结尾也插入特殊字符。这样可以将所有回文子串统一处理为奇数长度。
- 例如,将字符串"abc"转换为"#a#b#c#"
但是我倾向于在字符串左右边界加上两个不同的特殊字符,比如将“abc”转换为“^#a#b#c#$”。这样代码的容错率较大,判断时也不用特判边界情况。
2. 使用辅助数组P:
算法使用一个辅助数组P,P[i]表示以字符T[i]为中心的最长回文子串的半径(不包括字符T[i]本身)。
3. 中心扩展法:
算法通过扩展回文子串的中心,动态更新辅助数组P,并利用已经计算出的回文子串信息来减少重复计算。
4. 维护一个最右回文边界:
算法在遍历过程中,维护一个最右回文边界R和其对应的中心C。如果i在R的范围内,算法利用对称性来减少计算。
具体细节写在代码注释里了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 // 预处理函数:在每个字符之间插入特殊字符 4 string Preprocess(const string&s){ 5 string t; 6 if(s.empty()){ 7 t="^$";// 如果输入字符串为空,返回特殊处理后的字符串 8 return t; 9 } 10 t="^"; // 插入字符串开头的特殊字符 11 int n=s.size(); 12 for(int i=0;i<n;i++) 13 t=t+'#'+s[i];// 在每个字符前插入特殊字符# 14 t=t+"#$";// 插入字符串结尾的特殊字符 15 return t;// 返回预处理后的字符串 16 } 17 // Manacher算法:寻找最长回文子串 18 int Manacher(const string&t){ 19 int n=t.size(); 20 vector<int>p(n,0);// 辅助数组p,存储回文子串的半径 21 int R=0,C=0;// R为最右回文边界,C为回文中心 22 for(int i=1;i<n;i++){ 23 int mirr=C*2-i;//mirror 24 if(R>i) p[i]=min(R-i,p[mirr]); 25 while(t[i+p[i]+1]==t[i-p[i]-1]) p[i]++; 26 if(i+p[i]>R){ 27 R=i+p[i];// 更新最右回文边界R和回文中心C 28 C=i; 29 } 30 } 31 int ans=0; 32 // 找到p中的最大值,即最长回文子串的长度 33 for(int i=1;i<n;i++) ans=max(ans,p[i]); 34 return ans;// 返回最长回文子串的长度 35 } 36 int main(){ 37 string s,t; 38 cin>>s;// 读取输入字符串 39 t=Preprocess(s);// 调用预处理函数 40 cout<<Manacher(t);// 调用Manacher算法函数并输出结果 41 return 0; 42 }
by:AlenaNuna