poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

2024-09-09 16:58

本文主要是介绍poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

求一段字符串中的最长回文串。

因为数据量比较大,用原来的O(n^2)会爆。


小白上的O(n^2)解法代码:TLE啦~

#include<stdio.h>
#include<string.h>
const int Maxn = 1000000;char s[Maxn];int main()
{char e[] = {"END"};while(scanf("%s", s) != EOF){getchar();if(strcmp(e, s) == 0)break;int ncase = 1;int ans = 0;int len = strlen(s);for(int i = 0; i < len; i++){//奇数时for(int j = 0; i - j >= 0 && i + j < len; j++){if(s[i - j] != s[i + j])break;if(j * 2 + 1 > ans)ans = j * 2 + 1;}//偶数时for(int j = 0; i - j >= 0 && i + j + 1 <len; j++){if(s[i - j] != s[i + j + 1])break;if(j * 2 + 2 > ans)ans = j * 2 + 2;}}printf("Case %d: %d\n", ncase, ans);ncase++;}return 0;
}

然后找了百度,百度说要用Manacher算法可以用O(n)直接解决问题。


poj 3974 代码:

#include<stdio.h>
#include<string.h>
const int MaxN = 1000001;
char oldstr[MaxN];
char newstr[MaxN << 1];
int p[MaxN << 1];int min(int a, int b)
{return a < b ? a : b;
}int Pre()
{char head = '$';char middle = '#';char end = '?';int n = 0;newstr[n ++] = head;newstr[n ++] = middle;for(int i = 0; oldstr[i]!='\0'; i ++){newstr[n ++] = oldstr[i];newstr[n ++] = middle;}newstr[n ++] = end;return n;
}int Manacher(int n)
{int MaxID = 0;int MaxL = 0;int id = 1;p[0] = 0;for(int i = 1; i < n; i ++){if(MaxID > i)p[i] = min(p[2 * id - i], MaxID - i);elsep[i] = 1;while(newstr[i - p[i]] == newstr[i + p[i]])p[i] ++;if(p[i] + i > MaxID){MaxID = p[i] + i;id = i;}if(p[i] > MaxL){MaxL = p[i];}}return MaxL - 1;
}int main()
{char e[] = {"END"};int ncase = 1;while(scanf("%s", oldstr)!=EOF){if(strcmp(e, oldstr) == 0)break;printf("Case %d: ", ncase);ncase ++;int len = Pre();int ans = Manacher(len);printf("%d\n", ans);}return 0;
}

hdu 3068 数据比poj上弱,所以也顺利ac:

#include<stdio.h>const int MaxN = 110001;
char oldstr[MaxN];
char newstr[MaxN << 1];
int p[MaxN << 1];int min(int a, int b)
{return a < b ? a : b;
}int Pre()
{char head = '$';char middle = '#';char end = '?';int n = 0;newstr[n ++] = head;newstr[n ++] = middle;for(int i = 0; oldstr[i]!='\0'; i ++){newstr[n ++] = oldstr[i];newstr[n ++] = middle;}newstr[n ++] = end;return n;
}int Manacher(int n)
{int MaxID = 0;int MaxL = 0;int id = 1;p[0] = 0;for(int i = 1; i < n; i ++){if(MaxID > i)p[i] = min(p[2 * id - i], MaxID - i);elsep[i] = 1;while(newstr[i - p[i]] == newstr[i + p[i]])p[i] ++;if(p[i] + i > MaxID){MaxID = p[i] + i;id = i;}if(p[i] > MaxL){MaxL = p[i];}}return MaxL - 1;
}int main()
{while(scanf("%s", oldstr)!=EOF){int len = Pre();int ans = Manacher(len);printf("%d\n", ans);}return 0;
}

附:

我觉得百度文库上的这篇文章讲这个算法讲的很好,理解了一个下午,终于理解了。

求回文子串 O(n) manacher 算法:

回文串定义:“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。
回文子串,顾名思义,即字符串中满足回文性质的子串。

经常有一些题目围绕回文子串进行讨论,比如HDOJ_3068_最长回文,求最长回文子串的长度。朴素算法是依次以每一个字符为中心向两侧进行扩展,显然这个复杂度是O(N^2)的,关于字符串的题目常用的算法有KMP、后缀数组、AC 自动机,这道题目利用扩展KMP可以解答,其时间复杂度也很快O(N*logN)。但是,今天笔者介绍一个专门针对回文子串的算法,其时间复杂度为O(n),这就是manacher 算法。

大家都知道,求回文串时需要判断其奇偶性,也就是求aba 和abba 的算法略有差距。然而,这个算法做了一个简单的处理,很巧妙地把奇数长度回文串与偶数长度回文串统一考虑,也就是在每个相邻的字符之间插入一个分隔符,串的首尾也要加,当然这个分隔符不能在原串中出现,一般可以用‘#’或者‘$’等字符。

算法大致过程是这样。
先在每两个相邻字符中间插入一个分隔符,当然这个分隔符要在原串中没有出现过。
一般可以用’#’分隔。
这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了。
然后用一个辅助数组P记录以每个字符为中心的最长回文串的信息。
P[i]记录的是以字符str[i]为中心的最长回文串。
当以str[i]为第一个字符,这个最长回文串向右延伸了P[i]个字符。

为了防止越界,我们要在最后一个边界增加一个特殊标记,我们记为’$’.
当然,开始位置我们也最好标记一下,我们记为’?’。

这样一来,原来的奇数长度回文串还是奇数长度,偶数长度的也变成以‘#’为中心的奇数回文串了。
接下来就是算法的中心思想,用一个辅助数组P 记录以每个字符为中心的最长回文半径,也就是P[i]记录以Str[i]字符为中心的最长回文串半径。P[i]最小为1,此时回文串为Str[i]
本身。
我们可以对上述例子写出其P 数组,如下

新串:     $   #   a   #   b   #   a   #   a   #   b   #   ?
P[] :    0   1   2   1   4   1   2   5   2   1   2   1   0
i:       0   1   2   3   4   5   6   7   8   9  10  11  12
 

这样一来,原来的奇数长度回文串还是奇数长度,偶数长度的也变成以‘#’为中心的奇数回文串了。

接下来就是算法的中心思想,用一个辅助数组P 记录以每个字符为中心的最长回文半径,也就是P[i]记录以Str[i]字符为中心的最长回文串半径。

P[i]最小为1,此时回文串为Str[i]本身。

这里有一个性质:P[i]-1 就是以Str[i]为中心的回文串在原串当中的长度。证明:1.对于任一一个原始的str[i],p[i]都是偶数。看上面的abaab即可明白。2.由于以Str[i]为中心的最长回文串(不能使#)显然是奇数,我们设为L。

3.如果知道了L,则删除#后,剩下的就是原始字符串长度还是最长回文串,长度是(L – 1) / 2.

4.显然,L = p[i] * 2 -1;这样就证明了上面的性质。

5.所以,长度 = (p[i] * 2 -1 – 1) / 2 = P[i]-1。

核心代码:

int Manacher(int n)
{int MaxID = 0;int MaxL = 0;int id = 1;p[0] = 0;for(int i = 1; i < n; i ++){if(MaxID > i)p[i] = min(p[2 * id - i], MaxID - i);elsep[i] = 1;while(newstr[i - p[i]] == newstr[i + p[i]])p[i] ++;if(p[i] + i > MaxID){MaxID = p[i] + i;id = i;}if(p[i] > MaxL){MaxL = p[i];}}return MaxL - 1;
}

我们用MaxId 变量记录在求i 之前的回文串中,延伸至最右端的位置,同时用id 记录取这个MaxId 的id 值。
通过下面这句话,算法避免了很多没必要的重复匹配。

if(MaxID > i)p[i] = min(p[2 * id - i], MaxID - i);
解释:(理解的难点 自己模拟了好多次)

利用了回文串的对称性,如下图:


i = 2 * id - j 即为i 关于id 的对称点,根据对称性,P[j]的回文串也是可以对称到i 这边的,但是如果P[j]的回文串对称过来以后超过MaxId 的话,超出部分就不能对称过来了,如下
图,所以这里P[i]为的下限为两者中的较小者,p[ i ] = Min ( p[ 2 * id - i] , MaxId - i);


算法的有效比较次数为MaxId 次,所以说这个算法的时间复杂度为O(n)。

这篇关于poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1151749

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

poj3261(可重复k次的最长子串)

题意:可重复k次的最长子串 解题思路:求所有区间[x,x+k-1]中的最小值的最大值。求sa时间复杂度Nlog(N),求最值时间复杂度N*N,但实际复杂度很低。题目数据也比较水,不然估计过不了。 代码入下: #include<iostream>#include<algorithm>#include<stdio.h>#include<math.h>#include<cstring

csu1328(近似回文串)

题意:求近似回文串的最大长度,串长度为1000。 解题思路:以某点为中心,向左右两边扩展,注意奇偶分开讨论,暴力解即可。时间复杂度O(n^2); 代码如下: #include<iostream>#include<algorithm>#include<stdio.h>#include<math.h>#include<cstring>#include<string>#inclu

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

hdu 2093 考试排名(sscanf)

模拟题。 直接从教程里拉解析。 因为表格里的数据格式不统一。有时候有"()",有时候又没有。而它也不会给我们提示。 这种情况下,就只能它它们统一看作字符串来处理了。现在就请出我们的主角sscanf()! sscanf 语法: #include int sscanf( const char *buffer, const char *format, ... ); 函数sscanf()和

hdu 2602 and poj 3624(01背包)

01背包的模板题。 hdu2602代码: #include<stdio.h>#include<string.h>const int MaxN = 1001;int max(int a, int b){return a > b ? a : b;}int w[MaxN];int v[MaxN];int dp[MaxN];int main(){int T;int N, V;s