manacher算法是用来解决最长回文子串的。其中有一道题目是GDOI2015Day1的第一题。那么,manacher算法又是怎样的呢?
首先来看一看最长回文子串有怎样的解法:
O(n^3):枚举串的起止点,判断回文。
O(n^2):枚举中间点,扩展下去。
O(n):manacher算法,可以粗略地理解为O(n^2)的扩展。
首先,回文子串的长度,有时候是奇数,而有时候是偶数,为了统一——把偶数变成奇数——我们只需要在字符中间和开头结尾加上#号。
然后,我们设f[i]为以i为中心的最长回文子串的扩展长度,那这样,我们就要用O(n)的时间来求出所有的f[i]。怎么求出呢?
我们可以从f[1~i-1]中得到某些信息来推导f[i],从而减少O(n^2)算法的扩展次数。为了拿到更多的信息,需要把之前最大的f(i)值的i记为id,mx = id + f[id]。然后,再利用f[2id-i]得出答案。
这样,当mx>i时,我们可以得出 f[i] = min(f[2*id-i] , mx-i)。这里就是算法的重点了。为何是f[2*id-i]呢?
举个例子:
id | i | mx | |||||||
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
s | a | b | a | b | c | b | a | b | a |
f | 1 | 3 | 3 | 1 | 9 | 1 | 3 |
当求到i时,我们可以发现:f[2*id-i]值与f[1]值相同。而1 = 2 * id - i,所以,我们可以这样来推导f[i]而无需再扩展(得到答案)。
而当是部分答案时,则是mx - i,再扩展f[i]。
其他:f[i] = 1,扩展。
最后,我们得到一个几乎是线性时间的算法,这就是manacher算法。
【程序】
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
using namespace std;
string s;
void init()
{
string st;
cin>>st;
s = "";
int len = st.size();
//在开头结尾与字符之间加上'#'号
s += "$#";
for(int i=0;i<len;i++)
{
s += st[i];
s += "#";
}
return ;
}
void manacher(string s)//
{
int f[100010];
memset(f,0,sizeof(f));
int id = 0 , mx = 0 , len = s.size();
for(int i=0;i<len;i++)
{
if(mx > i)
f[i] = min(f[id*2 -i] , mx - i);//得到答案和得到部分答案。
else
f[i] = 1;
while(s[i - f[i]] == s[i + f[i]]) f[i] ++;//扩展
if(i + f[i] > mx)//维护id
{
mx = i + f[i];
id = i;
}
}
int maxn = -1 , j = 0;
for(int i=0;i<len;i++)//寻找最大回文子串
if(f[i] > maxn)
{
maxn = f[i];
j = i;
}
maxn -- ;
int st = j - maxn, en = j + maxn;
for(int i=st;i<=en;i++)//输出
if(s[i]!='#')
{
cout<<s[i];
}
return ;
}
int main()
{
init();
manacher(s);
return 0;
}