Manacher算法是大家常用的,用来求回文串系列问题的算法
具体算法过程,我不多说,不知道的,可以看:Manacher算法详述
通过Manacher算法之后,我们会得到几个比较重要的信息:
首先,就是这个字符串有无回文子串,以及最长的回文子串的长度。
其次,就是以某一点为中心的回文半径,也就是 p 数组。出题人比较喜欢在这个上面做文章
题型一:判断回文串,以及输出长度
首先是最基本的判断这个字符串是否有回文子串,以及输出最长的回文子串的长度。模板题目,一般不直接考察
就直接 Manahce算法 就行了
例题:最长回文
char s[maxn],str[maxn];
int p[maxn];
int Init()
{
int len = strlen(s);
str[0] = '@',str[1] = '#';
int j = 2;
for(int i=0;i<len;++i){
str[j++] = s[i];
str[j++] = '#';
}
str[j] = 0;
return j;
}
int manacher()
{
int mx,id;
mx = id = 0;
int ans = -1;
int len = Init();
for(int i=1;i<len;i++){
if(i<mx) p[i] = min(p[id*2-i],mx-i);
else p[i] = 1;
while(str[i+p[i]]==str[i-p[i]]) p[i]++;
if(p[i]+i>mx){
mx = p[i] + i;
id = i;
}
ans = max(p[i]-1,ans);
}
return ans;
}
然后既然出题人不想直接考察你的模板背的熟不熟
可以在这个回文串上做一下“手脚”……
就比如假设这是一个数字串,那么它可以给所求回文串加一些“限制”
什么要求从回文串开头到中心是递减趋势的,或者递增趋势的
以及非递增趋势,或者非递减趋势
试图在这上面难为你,
其实这也是难为不到我们的,如果对 Manacher算法 了解的比较透彻的的话。
它判断是否是回文串,以及求解回文半径主要体现在那个 while 循环了,
只要我们稍加修改就可以了
while(str[i+p[i]]==str[i-p[i]]) p[i]++;
这是最 low 的回文串半径的求解,意思就是 以 i 为中心的回文串的半径是 p[i],那么以 i 为中心的回文串长度就是 p[i]-1。
我们知道 Manacher算法的第一步就是处理字符串,插入无关字符,以确定对称抽。
那么如果这个回文串的左半部分是递减的话,也就是:5 & 4 & 3 & 3 & 4 & 5 这种格式
很明显就是 左开头的数字 要大于 左开头+2位置的数字,依次类推
也就是 str[i-p[i]]>str[i-p[i]+2]
也就是在那个 while 循环里加一个限制
while(str[i+p[i]]==str[i-p[i]] && str[i-p[i]]>str[i-p[i]+2]) p[i]++;
这样就能得到所要求的回文子串的最长长度了
例题:吉哥系列故事——完美队形II裸题,稍微一改,即可
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#include<queue>
#include<utility>
#include<set>
#include<stack>
#include<string>
#include<vector>
#define ll long long
#define llu unsigned long long
using namespace std;
const int maxn = 22000010;
int s[maxn],str[maxn];
int p[maxn];
int Init(int n)
{
int len = n;
str[0] = -1,str[1] = 10;
int j = 2;
for(int i=0;i<len;++i){
str[j++] = s[i];
str[j++] = 10;
}
str[j] = 0;
return j;
}
int manacher(int n)
{
int mx,id;
mx = id = 0;
int ans = 1;
int len = Init(n);
for(int i=1;i<len;i++){
if(i<mx) p[i] = min(p[id*2-i],mx-i);
else p[i] = 1;
while(str[i+p[i]]==str[i-p[i]]&&str[i-p[i]]<=str[i-p[i]+2]) p[i]++;
if(p[i]+i>mx){
mx = p[i] + i;
id = i;
}
ans = max(p[i]-1,ans);
}
return ans;
}
int main(void)
{
int t,n;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&s[i]);
}
printf("%d\n",manacher(n));
}
return 0;
}
题型二:考察你对于 数组p 的掌握
数组 p 是用来记录回文半径的,p[i]是代表处理后的字符串,以 i 点为中心的回文半径,那么显然 p[i]-1 就是原字符串中以 i 为中心的回文串长度。
出题人可能会:
字符串 s,输出 s 中最长的两个连续回文子串的长度(例如 abccbaabcac 答案是 9)
他让你找连续两段,而不是单纯的一段回文子串
按照 Manacher算法的传统步骤,
首先我们处理字符串:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
# | * | a | * | b | * | c | * | c | * | b | * | a | * | b | * | c | * | a | * | c | * |
假设 ans 是前一段回文串的长度,那么 ans/2*3 就是最终答案
我们从 i=1,开始依次 +=2 ,直到字符串结束,
假设 以 i 为中心的回文串的长度是 p[i],那么如果 [i,i+p[i]-1] 这一段内存在点 j,p[j]>=j-i+1,则说明存在题目要求的连续两段回文子串,那么 ans = max(ans,j-i)。
为什么这么说呢
首先 我们可以确定 [i-p[i]+1,i] 和 [i,i+p[i]-1] 这一段是对称的
那么 如果说在 [i,i+p[i]-1] 这一段内存在点 j,使得 [i,j] 和 [j,i+p[j]-1] 对称的话
我们就得到了连续两段回文串,长度是 (j-i)/2*3。
int ans = 0;
for(int i=1;i<n;i+=2){
for(int j=i+p[i]-1;j-i>ans;j-=2){ //因为找最大ans,所以就从右往左推
if(p[j]>=j-i+1&&ans<j-i){
ans = j-i;
break;
}
}
}
例题:Hotaru’s problem
Problem Description
Hotaru Ichijou recently is addicated to math problems. Now she is playing with N-sequence.
Let’s define N-sequence, which is composed with three parts and satisfied with the following condition:
- the first part is the same as the thrid part,
- the first part and the second part are symmetrical.
for example, the sequence 2,3,4,4,3,2,2,3,4 is a N-sequence, which the first part 2,3,4 is the same as the thrid part 2,3,4, the first part 2,3,4 and the second part 4,3,2 are symmetrical.
Give you n positive intergers, your task is to find the largest continuous sub-sequence, which is N-sequence.
Input
There are multiple test cases. The first line of input contains an integer T(T<=20), indicating the number of test cases.
For each test case:
the first line of input contains a positive integer N(1<=N<=100000), the length of a given sequence
the second line includes N non-negative integers ,each interger is no larger than 109 , descripting a sequence.
Output
Each case contains only one line. Each line should start with “Case #i: ”,with i implying the case number, followed by a integer, the largest length of N-sequence.
We guarantee that the sum of all answers is less than 800000.
Sample Input
1
10
2 3 4 4 3 2 2 3 4 4
Sample Output
Case #1: 9
AC代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#include<queue>
#include<utility>
#include<set>
#include<stack>
#include<string>
#include<vector>
#define ll long long
#define llu unsigned long long
using namespace std;
const int maxn = 210000;
int s[maxn],str[maxn],n;
int p[maxn];
int Init()
{
str[0] = -1,str[1] = -2;
int j = 2;
for(int i=0;i<n;++i){
str[j++] = s[i];
str[j++] = -2;
}
str[j] = 0;
return j;
}
void manacher()
{
int mx,id;
mx = id = 0;
int len = Init();
n = len;
for(int i=1;i<len;i++){
if(i<mx) p[i] = min(p[id*2-i],mx-i);
else p[i] = 1;
while(str[i+p[i]]==str[i-p[i]]) p[i]++;
if(p[i]+i>mx){
mx = p[i] + i;
id = i;
}
}
}
int main(void)
{
int t;
scanf("%d",&t);
for(int cas=1;cas<=t;cas++){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&s[i]);
}
manacher();
int ans = 0;
for(int i=1;i<n;i+=2){
for(int j=i+p[i]-1;j-i>ans;j-=2){
if(p[j]>=j-i+1&&ans<j-i){
ans = j-i;
break;
}
}
}
printf("Case #%d: %d\n",cas,ans/2*3);
}
return 0;
}
来源:CSDN
作者:╰⋛⋋⊱⋋吳⋌⊰⋌⋚╯
链接:https://blog.csdn.net/qq_43724031/article/details/104044901