字符串算法——manacher(马拉车)1/10

manacher算法用处

1.可以求一个字符串内最长回文字符串的长度,还能得到最长回文字符串是什么。

2.可以求出一个字符串内有多少个回文字符串(同类型的算多个)。ps:如果要求不同类型回文字符串的数量可以使用dp,回文树,manacher+hash。

时间复杂度

因为在计算一个特定位置的答案时我们总会运行朴素算法,所以一眼看去该算法的时间复杂度为线性的事实并不显然。

然而更仔细的分析显示出该算法具有线性复杂度。实际上,注意到朴素算法的每次迭代均会使r增加 1,以及 在算法运行过程中从不减小。这两个观察告诉我们朴素算法总共会进行 O(n) 次迭代。

Manacher 算法的另一部分显然也是线性的,因此总复杂度为 O(n) 。

form:[oiwiki](Manacher - OI Wiki (oi-wiki.org)

原理

参考该[博客](题解 P3805 [[模板]manacher算法] - 谁是鸽王 的博客 - 洛谷博客 (luogu.com.cn)

核心思想就是利用已经暴力找到的回文串,加上回文串关于中心对称的性质,来快速得到部分回文字符串的长度,简化了暴力的规模。

算法模板


string s;//原串
char str[N];//处理后的原串
int p[N]//存储以每个点为中心的最长回文子串的半径
int ans;//代表原串中最长的回文串的长度
int sum;//代表原串中回文串的个数(同类型算多次)
int init()//init操作可以让manacher同时求解奇数个数回文串和偶数个数回文串
{
    int len=s.length();
    str[0]='^';
    str[1]='$';
    int j=2;
    for (int i=0;i<len;i++)
    {
        str[j++]=s[i];
        str[j++]='$';
    }
    str[j]='&';
    return j;
}
int Manacher()
{
    int len=change(),mid=1,r=1,ans=-1;//mid表示回文串的中心,r表示已知回文串的最右端的位置
    for (int i=1;i<len;i++)//对于每个点,我们寻找以当前点为中心的最长回文子串的半径
    {
        if (i<r)//如果当前点的位置小于r,表示我们可以通过已知的回文串的信息,来更新当前点的信息,减少暴力的次数
            p[i]=min(r-i,p[mid*2-i]);因为无法保证以当前点的对称点为中心的回文串的长度一定比当前点到最右端的位置小,所以二者取最小值,不确定的地方再交给暴力来确定
        else
            p[i]=1;//超过最右端时,先设为1,之后暴力找
        while (s[i-p[i]]==s[i+p[i]])//暴力搜索过程
            p[i]++;
        if (r<i+p[i])//更新最右端
        {
            mid=i;
            r=i+p[i];
        }
        sum+=p[i]/2;代表原串中回文串的个数(同类型算多次)
        ans=max(ans,p[i]-1);
    }
    return ans;//ans代表原串中最长的回文串的长度
}

输出最长回文串模板

void printSub(int C, int R){//C代表中心,R代表半径
    for(int i = C - R+1  ; i < C+ R;){
        cout<<str[i];
        i+=2;
    }
}

void printmax(int k)//打印回文串
{
        if(p[k] >=1)
            printSub(k, p[k]);
}

【模板】manacher 算法

题目描述

给出一个只由小写英文字符 $ \texttt a,\texttt b,\texttt c,\ldots\texttt y,\texttt z $ 组成的字符串 S S S ,求 S S S 中最长回文串的长度 。

字符串长度为 n n n

输入格式

一行小写英文字符 a , b , c , ⋯   , y , z \texttt a,\texttt b,\texttt c,\cdots,\texttt y,\texttt z a,b,c,,y,z 组成的字符串 S S S

输出格式

一个整数表示答案。

样例 #1

样例输入 #1

aaa

样例输出 #1

3

提示

1 ≤ n ≤ 1.1 × 1 0 7 1\le n\le 1.1\times 10^7 1n1.1×107

AC代码

const int N = 3e7;//因为N的范围问题,re了好几次,能开大最好开到最大,因为str需要两倍的空间
char s[N];
char str[N];
int p[N];
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++]='#';
	}
	s[j]='&';
	return j;
} 
int manacher()
{
	int r=-1,mid=0;
	int len=init();
	int ans=0;
	for(int i=0;i<len;i++)
	{
		if(i<r)
		p[i]=min(r-i,p[2*mid-i]);
		else
		p[i]=1;
		while(str[i-p[i]]==str[i+p[i]]) p[i]++;
		if(p[i]+i>r)
		{
			mid=i;
			r=p[i]+i;
		}
		ans=max(ans,p[i]-1);
	}
	return ans;	
}
void solve()
{
	scanf("%s",s);
	cout<<manacher();
}

博客内容及模板仅供个人学习使用,如有错误,欢迎指正!

猜你喜欢

转载自blog.csdn.net/qq_57109165/article/details/126376454