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;
}

猜你喜欢

转载自blog.csdn.net/qq_46527915/article/details/124833606