ACM入门之manacher(马拉车)算法

Posted 辉小歌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ACM入门之manacher(马拉车)算法相关的知识,希望对你有一定的参考价值。

本文参考了:FREEH在https://www.luogu.com.cn/problem/solution/P3805中的题解。




主要用途:O(n)的时间内求一个字符串的最大回文长度。或者 求其所有子串中回文串的个数。
算法过程:

  • 预处理。
    由于回文串分为偶回文串和奇回文串,奇偶判断起来比较麻烦,因此我们可以在字符串的首、尾以及各个字符之间添加一些“神奇”字符(不 妨使用$),但是要注意字符串的首添加的字符必须区别于各个字符之间的字符。不难发现,修改后的字符串都变成了奇字符串。
  • 几个变量的含义
    • p[i]表示以字符i为回文中心的最长回文串的半径,不难发现,p[i]-1就是字符串中最长回文串的长度(因为要去除$)。
    • mx:表示目前找到的回文串的右端的最右是mx
    • mid:表示目前找到的回文串的中心是mid

例图说明:

判断字符串的最长回文串的长度的板子。

const int N=1e7*3+10;
int  n,p[N];
char a[N],b[N];
int init()

	n=strlen(a);
    int k = 0;
    b[k ++ ] = '^', b[k ++ ] = '#';
    for (int i = 0; i < n; i ++ ) b[k ++ ] = a[i], b[k ++ ] = '#';
    b[k]='\\0';
    return k;

void manacher()

    scanf("%s",a);
    int mr = 0, mid;
    int max_len=0; 
    n=init();
    for (int i = 1; i < n; i ++ )
    
        if (i < mr) p[i] = min(p[mid * 2 - i], mr - i);
        else p[i] = 1;
        while (b[i - p[i]] == b[i + p[i]]) p[i] ++ ;
        if (i + p[i] > mr)
        
            mr = i + p[i];
            mid = i;
        
        max_len = max(max_len, p[i] - 1);
    
    cout<<max_len;

判断数字数组的最长回文串的长度的板子。

const int N=1e6*2+10;
int n,p[N],a[N],b[N];
int init()

    int k = 0;
    b[k ++ ] = 1e9+1, b[k ++ ] = 1e9+2;
    for (int i = 0; i < n; i ++ ) b[k ++ ] = a[i], b[k ++ ] = 1e9+2;
    return k;

void manacher()

	cin>>n;
	for(int i=0;i<n;i++) cin>>a[i];
    int mr = 0, mid;
    int max_len=0; 
    n=init();
    for (int i = 1; i < n; i ++ )
    
        if (i < mr) p[i] = min(p[mid * 2 - i], mr - i);
        else p[i] = 1;
        while (b[i - p[i]] == b[i + p[i]]) p[i] ++ ;
        if (i + p[i] > mr)
        
            mr = i + p[i];
            mid = i;
        
        max_len = max(max_len, p[i] - 1);
    
    cout<<max_len<<endl;

例题一:

https://www.luogu.com.cn/problem/P3805

#include<bits/stdc++.h>
using namespace std; 
const int N=1e7*3+10;
int  n,p[N];
char a[N],b[N];
int init()

	n=strlen(a);
    int k = 0;
    b[k ++ ] = '^', b[k ++ ] = '#';
    for (int i = 0; i < n; i ++ ) b[k ++ ] = a[i], b[k ++ ] = '#';
    b[k]='\\0';
    return k;

void manacher()

    scanf("%s",a);
    int mr = 0, mid;
    int max_len=0; 
    n=init();
    for (int i = 1; i < n; i ++ )
    
        if (i < mr) p[i] = min(p[mid * 2 - i], mr - i);
        else p[i] = 1;
        while (b[i - p[i]] == b[i + p[i]]) p[i] ++ ;
        if (i + p[i] > mr)
        
            mr = i + p[i];
            mid = i;
        
        max_len = max(max_len, p[i] - 1);
    
    cout<<max_len;

int main(void)

	manacher();
	return 0;

例题二:

p[i] 表示以字符i为回文中心的最长回文串的半径[i-p[i]+1,i]区间内和其以i为对称轴对称的都是回文串。区间加用差分。

#include<bits/stdc++.h>
using namespace std; 
const int N=1e6*2+10;
int n,p[N];
int a[N],b[N],d[N];
int init()

    int k = 0;
    b[k ++ ] = 1e9+1, b[k ++ ] = 1e9+2;
    for (int i = 0; i < n; i ++ ) b[k ++ ] = a[i], b[k ++ ] = 1e9+2;
    return k;

void manacher()

    int mr = 0, mid;
    int max_len=0; 
    n=init();
    for (int i = 1; i < n; i ++ )
    
        if (i < mr) p[i] = min(p[mid * 2 - i], mr - i);
        else p[i] = 1;
        while (b[i - p[i]] == b[i + p[i]]) p[i] ++ ;
        if (i + p[i] > mr)
        
            mr = i + p[i];
            mid = i;
        
        max_len = max(max_len, p[i] - 1);
    

void add(int l,int r,int c)

	d[l]+=c;
	d[r+1]-=c;

int main(void)

	cin>>n;
	for(int i=0;i<n;i++) cin>>a[i];
	manacher();
	for(int i=1;i<n;i++)
	
		int l=i-p[i]+1,r=i;
		add(l,r,1);
	
	for(int i=1;i<n;i++) d[i]+=d[i-1];
	for(int i=1;i<n;i++) if(i%2==0) cout<<d[i]<<" ";
	return 0;

以上是关于ACM入门之manacher(马拉车)算法的主要内容,如果未能解决你的问题,请参考以下文章

Manacher's Algorithm 马拉车算法

manacher马拉车算法

字符串-Manacher算法(你知道马拉车算法吗?)

字符串-Manacher算法(你知道马拉车算法吗?)

Manacher(马拉车算法)

Manacher 马拉车算法