manacher(马拉车算法)

今天学长讲了讲马拉车算法,开始听的一脸懵,然后等结束了自己拿着草纸画了画,又看了看代码,开始逐渐明白马拉车算法的奥义

首先是将该字符串增加n+1个#,这样无论原字符串中元素个数是奇还是偶,最后的字符串中的元素都是奇数个

		int n=strlen(a);
		int T=2;
		manacher[0]='$';//初始化边界防止越界
		manacher[1]='#';
		for(int i=0;i<n;i++){
			manacher[T++]='#';
			manacher[T++]=a[i];
		}
		manacher[T++]='0'; //初始化边界防止越界

接下来就是马拉车的核心代码了

int maxid=0; //用来记录最大的遍历位置
int id=0; //用于记录对称轴
for(int i=0;i<k;i++){
  if(maxid>i){  //如果最大的遍历位置的点大于当前要求的点的位置
  	p[i]=min(maxid-i,p[2*id-i]); //当前遍历过的位置和它对面的对称坐标取最小值;因为之后还要对p[i]进行加的操作,而且p[i]的初始值不能超过maxid-i(因为当前搜索到的最大值-i就等于当前已经知道的p[i]的最大可能情况)
  }else{//否则的话从1开始
  	p[i]=1;
  }
  while(manacher[i-p[i]]==manacher[i+p[i]])p[i]++;//如果满足回文拓展指针大小
  if(i+p[i]>maxid){//如果是最大,那就记录最大
    maxid=i+p[i];
    id=i;
  }
}

manacher 扩展序列与原序列的关系
(1):新串中的以位置i为中心的回文串 i ,对应原始串的( (i-len[i])/2 , (i+len[i])/2-2 )
(2):以i为中心的最长回文的长度为p[i]-1;
容易证明,manner中的回文串中#会比字母多一,而且若算上#,回文串的长度为2*p[i]-1,所以可以得出字母的长度为p[i]-1;

接下来来看看板子题吧:
回文串
没啥好说的,标标准准的板子题;

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#include<cmath>
#include<queue>
#include<stack>
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
const int N=1400;
char a[N];
char manacher[N<<1];
int p[N<<1];
int main()
{
	while(scanf("%s",a)!=EOF){
		memset(p,0,sizeof p);
		memset(manacher,0,sizeof manacher);
		int T=2;
		manacher[0]='$';
		manacher[1]='#';
		int n=strlen(a);
		for(int i=0;i<n;i++){
			manacher[T++]='#';
			manacher[T++]=a[i];
		}
		manacher[T++]='0';  //初始化数组
		int maxid=0;
		int id=0;
		int ans=0;
		for(int i=0;i<T;i++){
			if(maxid>i){
				p[i]=min(maxid-i,p[id*2-i]);
			}
			else{
				p[i]=1;
			
			}
			while(manacher[i-p[i]]==manacher[i+p[i]])
			{
				if(i+p[i]>=T){  //判断不超过字符串
				 
					break;
				}
				p[i]++;
			 } 
			if(i+p[i]>maxid){
				maxid=i+p[i];
				id=i;
			}
			ans=max(ans,p[i]-1);
		} 
		cout<<ans<<"\n";
	}
}

来看看稍微扩展一点
马拉车简单扩展

这道题其实也算一个板子题,但是稍微不一样的就是加了一个判断,就是从左到中间的序列是非递减的
这时候其实只需要将‘#’换成小于50的数即可,因为要满足从左到中间的序列是非递减的,同时加入一个judge记录上一个人的身高就可以了,若是manacher[i]<=50,此时只需要把judge换成任意一个大于250的数就可以了

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#include<cmath>
#include<queue>
#include<stack>
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
const int N=1e5+10;
int a[N];
int manacher[N<<1];
int q[N<<1];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--){
		memset(q,0,sizeof(q));
		int n;
		scanf("%d",&n);
		for(int i=0;i<n;i++){
			scanf("%d",&a[i]);
		}
		manacher[0]=-1;
		manacher[1]=10;
		int k=2;
		for(int i=0;i<n;i++){
			manacher[k++]=a[i];
			manacher[k++]=10;
		}
		manacher[k++]=-2;
		int ans=0;
		int maxid=0;
		int id=0;
		int judge=0;
		for(int i=0;i<k;i++){
			if(maxid>i){
				q[i]=min(maxid-i,q[id*2-i]);
			}
			else{
				q[i]=1;
			}
			if(manacher[i]==10){
				judge=260;
			}
			else{
				judge=manacher[i];
			}
		
			while(manacher[i-q[i]]==manacher[i+q[i]]&&manacher[i-q[i]]<=judge){
					if(manacher[i-q[i]]!=10){
					judge=manacher[i-q[i]];
					}
					q[i]++;
			
			}
			if(i+q[i]>maxid){
				maxid=i+q[i];
				id=i;
			}
			ans=max(ans,q[i]-1);
		}
		cout<<ans<<"\n";
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值