此题就是问一个只含有字母的的字符串中的最长的回文串是什么。如果有多解的话,打印出最先出现的那个。
首先考虑到回文串的特点。那么我们的肯定想到将字符串反转,并加到原字符串后面,用一个字符隔开。然后就是求后缀的最长前缀。
这里最长前缀是我们已知道的算法。这里我们不适用RMQ来解。只要一个O(n)的时间就可以解决了。遍历一下height数组就行了。
具体见代码。
//
// main.cpp
// ura 1297. Palindrome -----后缀数组的妙用
//
// Created by XD on 15/9/5.
// Copyright (c) 2015年 XD. All rights reserved.
//
#include <iostream>
#include <string>
#include <queue>
#include <stack>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include<vector>
#include <string.h>
#include <string>
#include <algorithm>
#include <set>
#include <map>
#include <cstdio>
using namespace std ;
const int maxn = 3000 + 10 ;
int s[maxn] ;
int t[maxn],t1[maxn] , r[maxn] ,height[maxn],c[maxn] ,sa[maxn];
int min(int x , int y )
{
return x >y ?y:x ;
}
int max(int x, int y )
{
return x > y ?x:y ;
}
void build_sa(int n ,int m)
{
int *x =t ,*y=t1 ,*temp ;
for(int i = 0 ; i < m ; i++) c[i] = 0 ;
for(int i = 0 ; i < n;i++) c[x[i] =s[i]]++ ;
for(int i = 1 ; i < m ;i++) c[i]+=c[i-1] ;
for(int i = n-1;i>-1;i--) sa[--c[x[i]]] = i ;
for(int k = 1; k <= n ;k<<=1)
{
int p = 0 ;
for(int i= n-k ; i < n ;i++ ) y[p++] = i ;
for(int i= 0 ;i < n ;i++) if(sa[i] >= k ) y[p++] = sa[i]- k ;
for(int i = 0 ; i < m ; i++) c[i] = 0;
for(int i =0 ; i < n ; i ++) c[x[y[i]]]++ ;
for(int i = 1 ; i < m ; i++) c[i]+=c[i-1] ;
for(int i = n -1; i > -1;i--) sa[--c[x[y[i]]]] = y[i] ;
temp = x ; x =y ; y =temp ;
p = 1 ; x[sa[0]] = 0 ;
for(int i = 1 ; i < n ; i++)
{
x[sa[i]] = (y[sa[i]] == y[sa[i-1]] && y[sa[i] + k ] == y[sa[i-1] + k ])?p-1:p++ ;
}
if (p >= n ) {
break ;
}
m = p;
}
}
void getHeight(int n )
{
for(int i = 0 ; i < n ; i ++)
{
r[sa[i]] = i ;
}
int k = 0;
for (int i = 0 ; i <n-1 ; i++) {
if (k) {
k-- ;
}
int j = sa[r[i]-1] ;
while (s[i+k] == s[j+k]) {
k++ ;
}
height[r[i]] = k ;
}
}
int main() {
char t[maxn] ;
gets(t) ;
int len=(int) strlen(t) ;
for (int i = 0 ; i < len; i++) {
s[i] = t[i] ;
}
if (len == 0 ) {
printf("\n") ;
return 0 ;
}
s[len] = 1 ;
for (int i = 0 ; i < len; i++) {
s[len+1 + i ] = t[len-1-i] ;
}
s[2 * len+1] = 0 ;
build_sa(2*len+2, 130) ;//构建后缀数组
getHeight(2*len +2) ;//获取height数组
//下面为求最长的前缀。
/*对于按照字典序排列的一系列后缀,其中第i个后缀,与这个后缀具有最长前缀的后缀肯定是相邻的后缀(也就是rank[i] +- 1的后缀.
上面的结论可以使用反正法证明。假设存在这样的后缀t使得rank[t] > rank[i] + 1 且小于rank[i]-1那么对于rank[t] 和rank[i]之间的后缀则会出现矛盾。用后缀大小关系就可推出矛盾。
*/
int start = 0 , ans =1;
for (int i = 3; i <2*len+2; i++) {
int t1 = min(sa[i],sa[i-1]) ;int t2 = max(sa[i],sa[i-1]) ;
//这个if的条件十分重要,首先两个后缀得 一个来自原串,一个来自反转之后的串。另外两个后缀起始位置之间的关系也十分重要。这个是确定这两个后缀的前缀在原串时同一个区段的串。
if (t1 < len &&t2 >len
&& t2-t1==len -t1+ len - (t1 + height[i]-1)) {
if(height[i] == ans )
{
start = min(start,min(sa[i],sa[i-1])) ;
}
else if (height[i] > ans)
{
start = min(sa[i],sa[i-1]) ;
ans = height[i] ;
}
}
}
for (int i = start; i-start < ans; i++) {
printf("%c" , s[i]) ;
}
printf("\n") ;
return 0;
}