题意:给定一串字符串,求其中最长回文的长度,输出之。
题解:刚看到这题,以为用扩展kmp就可以解决,扩展kmp的时间复杂度是O(n*logn),对于此题,n最大为11W,但是测试用例有120组。一组测试用例大概就一两百万的处理量的样子,而一共有120组,那么总的处理就可能达到几个亿了,估计这样的复杂度是吃不消的。而且也看到别人说这题卡logn的时间(他是用后缀数组求的),所以必须要有一个O(n)的算法,很巧的是,我在我一个同学那里看到了这样的一个算法,很不错,名字就叫做manacher算法。
其实manacher算法还是蛮容易懂的,利用的原理和扩展kmp有点相似,所以看一下就立马懂了。下面我讲一点重要的部分。
manacher算法的重点是对已经处理的点的最长回文的值的有效利用,
这个算法是从头开始一个一个的计算每一点的最长回文的长度,
而在最开始的时候要对字符串进行一个处理,就是在每两个字符的中间加一个字符,这个字符要是字符串中所没有的,在字符串的两端也要加。加这个的原因在于,我们要讲奇数长度回文的字符串和偶数长度回文的字符串统一处理,这样就可以再线性时间内算出最长回文的长度了。
对于我们已经处理的字符,我们知道了他们的最长回文长度,我们要记下两个值,一个是最长回文所能到达的最右边的下标mx,以及那个回文的字符的下标id。
当我们处理第i个字符时,会出现两种情况,一种是i>mx,这时的i是没处理没比较过的,所以令其最长长度为i,然后为两边一个一个的比较。
另一种是i<mx,这时我们知道以id为对称轴的i的左边的下标是j = 2*id - i,其最长回文是p[j];
则有两种情况:
(2*id - mx ) (j - p[j]) j (j + p[j]) id i mx
从左到右,我们可以看到,以为id这个点的回文可以到达mx,所以id到mx这段的字符和2*id-mx到id是相同的,只不过方向相反。
现在我们可以看到对于j点,在字符串中他的字符肯定是和i点的字符时一样的(根据id对称)。
那么i的回文也会和j的回文有一部分类似。
当j-p[j] >= 2*id - mx时,也就是对于j点的回文长度他是没有超过id的回文长度的范围的,所以关于轴对称的i点他的回文长度就等于就j点的回文长度。
当j-p[j] < 2*id - mx 时,根据轴对称,我们知道i到mx这一段绝对是包括在i的回文里面的,所以长度要加上这个值,然后再往后两边依次逐个对比,相等长度就加1,直到不等。
然后跟新id和mx就行了。
最后我们求出最大的一个p[i]-1就可以了。因为两边加了一个字符要减去1.
在对原字符串处理时,应在最开头的位置加一个特殊的字符,是后面不可能出现的字符。这样做的原因是防止对比时越界。
代码:
#include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAX_ = 110010; char str[MAX_*2], str1[MAX_]; int p[MAX_*2]; int main() { int mx,id; while(~scanf("%s",str1)) { int t = 1,len = strlen(str1); str[0] = '$'; for(int i = 0; i < len; ) { if(t & 1) str[t++] = '#'; else { str[t++] = str1[i++]; } } str[t++] = '#'; str[t] = '\0'; mx = 0; for(int i = 1; i < t; i++) { if(i < mx) { p[i] = min(p[id*2-i],mx-i); } else p[i] = 1; for(; str[p[i]+i] == str[i-p[i]]; p[i]++); if(p[i] + i > mx) { mx = p[i] + i; id = i; } } int ans = -1; for(int i = 1; i < t; i++){ if(p[i] - 1 > ans){ ans = p[i] - 1; } } printf("%d\n",ans); } return 0; }