很长时间没有进展了 今天终于把这道题做了 不过java的代码在第8个数据时还是超时
方法为先写一个RMQ(在数组区间中访问最小元素), 然后用RMQ写一个SuffixArray(后缀数组), 再用SuffixArray写一个LongestPalindrome(在数组中寻找最长回文)
关于RMQ见:http://www.cnblogs.com/SDJL/archive/2008/10/11/1308567.html
关于SuffixArray见:http://www.cnblogs.com/SDJL/archive/2008/10/30/1323175.html
这道题做得太累了,实在不想多说,不想学习后缀数组的朋友就不要看了,代码贴上来,因为太长,所以就折叠了!
Code

/**//*
ID: sdjllyh1
PROG: calfflac
LANG: JAVA
complete date: 2008/10/30
efficiency: O(N * lgN)
author: LiuYongHui From GuiZhou University Of China
more article: www.cnblogs.com/sdjls

case8 超时
*/

import java.io.*;
import java.util.*;

public class calfflac


{
private static String datas = "";
private static int startIndex;
private static int endIndex;
private static int palindromeLength;

public static void main(String[] args) throws IOException

{
init();
run();
output();
System.exit(0);
}

private static void run()

{
int clearLength = 0;
int[] originalIndex = new int[datas.length()];
for (int i = 0; i < datas.length(); i++)

{
if (Character.isLetter(datas.charAt(i)))

{
originalIndex[clearLength] = i;
clearLength++;
}
}

char[] clearDatas = new char[clearLength];
for (int i = 0; i < clearLength; i++)

{
clearDatas[i] = Character.toLowerCase(datas.charAt(originalIndex[i]));
}

LongestPalindrome lp = new LongestPalindrome(clearDatas);
startIndex = originalIndex[lp.getStartIndex()];
endIndex = originalIndex[lp.getEndIndex()];
palindromeLength = lp.getEndIndex() - lp.getStartIndex() + 1;
}

private static void init() throws IOException

{
BufferedReader f = new BufferedReader(new FileReader("calfflac.in"));
String str;
while ( (str = f.readLine()) != null)

{
datas += str;
}
f.close();
}

private static void output() throws IOException

{
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("calfflac.out")));
out.println(palindromeLength);
for (int i = startIndex; i <= endIndex; i++)

{
out.print(datas.charAt(i));
if ((i+1) % 80 == 0 )

{
out.println();
}
}
out.println();
out.close();
}
}


class LongestPalindrome


{
private SuffixArray sa;
private int startIndex;
private int endIndex;

public LongestPalindrome(char[] datas)

{
char[] newDatas = new char[datas.length*2+1];
int length = datas.length;
System.arraycopy(datas, 0, newDatas, 0, length);
newDatas[length] = '#';
for (int i = 0; i < length; i++)

{
newDatas[i + length + 1] = datas[length - i - 1];
}
sa = new SuffixArray(newDatas);

int best = 0;
for (int i = 0; i < length; i++)

{
if (sa.getLCP(i, length * 2 - i) * 2 - 1 > best)

{
best = sa.getLCP(i, length * 2 - i) * 2 - 1;
startIndex = i - sa.getLCP(i, length * 2 - i) + 1;
endIndex = i + sa.getLCP(i, length * 2 - i) - 1;
}
if (i > 0 && sa.getLCP(i, length * 2 - i + 1) * 2 > best)

{
best = sa.getLCP(i, length * 2 - i + 1) * 2;
startIndex = i - sa.getLCP(i, length * 2 - i + 1);
endIndex = i + sa.getLCP(i, length * 2 - i + 1) - 1;
}
}
}

public int getStartIndex()

{
return this.startIndex;
}
public int getEndIndex()

{
return this.endIndex;
}
public char getData(int index)

{
return sa.getData(index);
}
}


class SuffixArray


{
private char[] datas;//存放字符串数据
private int[] position;//position[3]=5表示第3名的后缀数组开头位置为5
private int[] rank;//position的反函数,rank[5]=3表示开头位置为5的后缀数组名次等于3
private RMQ height;//为什么用height? 我也不知道,看论文,height[i]表示第i名与第i-1名后缀串的lcp
private int length;//数组的长度
private int k;//用来计算k前缀名次数组与后缀数组
public SuffixArray(char[] datas)

{
this.datas = datas;
this.length = datas.length;

computePosAndRank();
computeHeight();
}

//O(1)
//获得suffix(firstIndex)与suffix(secondIndex)的lcp
public int getLCP(int firstIndex, int secondIndex)

{
int lcp;
if (firstIndex == secondIndex)

{
lcp = this.length - firstIndex;
}
else

{
int minRank = Math.min(this.rank[firstIndex], this.rank[secondIndex]);
int maxRank = Math.max(this.rank[firstIndex], this.rank[secondIndex]);
lcp = this.height.getMinimum(minRank + 1, maxRank);
}
return lcp;
}

public char getData(int index)

{
return this.datas[index];
}
//根据位置position获得suffix(position)的名次
public int getRank(int position)

{
return this.rank[position];
}

//根据名次rank获得第rank名的位置
public int getPosition(int rank)

{
return this.position[rank];
}

// O(N*logN)
//计算位置数组与后缀数组
private void computePosAndRank()

{
this.position = new int[this.length];
this.rank = new int[this.length];
//计算1前缀名次数组
computeRank_1();
//用1前缀名次数组计算1前缀位置数组
computePos_1ByRank_1();

//依次计算2、4、8……名次数组与后缀数组
k = 1;
while (k < this.length)

{
computePos_2kByRank_k();
computeRank_2kByPos_2kAndRank_k();
k += k;
}
}

//O(N)
//用k前缀名次数组计算2k前缀位置数组
private void computePos_2kByRank_k()

{
//计数排序中用于统计每个比较值出现的次数,与统计小于等于某个数的比较值出现的次数
int[] c = new int[this.length];
//计数排序中保存已排序的值
int[] b = new int[this.length];
//由于基数排序中需要用到两次计数排序,而第一次计数排序时由于被排序值的位置改变,
//比较值的位置也随着改变,因此需要使用newRank[]来保存新的比较值,用于第二次计数排序
int[] newRank = new int[this.length];

//===============开始第一次计数排序==============

//在计算2k前缀名次数组时,最后k个后缀的名次必然是唯一的,因此第一次计数排序时可以不用对这k个后缀排序,
//但是我们需要把这k个后缀放在“名次”的前面(考虑排序“acc”,k=2),且需要同时移动比较数据
for (int i = 0; i < this.k; i++)

{
this.position[i] = this.length - this.k + i;
}
System.arraycopy(this.rank, this.length - this.k, newRank, 0, this.k);

//注意,从第0个后缀数组开始名次分别为k、k+1、k+2……,因此如下初始化被排序值
for (int i = this.k; i < length; i++)

{
this.position[i] = i - this.k;
}

//置c[]为0,开始统计比较值出现的次数
Arrays.fill(c, 0);
for (int i = 0; i < this.length - this.k; i++)

{
c[this.rank[i+k]]++;
}

//统计小于等于某个值的比较值个数
for (int i = 1; i < this.length; i++)

{
c[i] = c[i] + c[i - 1];
}

//开始第一次计数排序,对前length - k 个后缀从最后一个开始放到指定位置
for (int i = this.length - 1 - this.k; i >= 0; i--)

{
//this.rank[i + k]表示第i个被排序值对应的比较值
//c[this.rank[i + k]]表示小于等于这个比较值的个数
//c[this.rank[i + k]] - 1 + k,因为我们把前k个位置留给了最后k个后缀,因此需要加上偏移量k,因为从0开始计数,所以保存的位置减1
//b[c[this.rank[i + k]] - 1 + k]就是因该被放置的地方
//this.position[i + k]为第i个被排序值
b[c[this.rank[i + this.k]] - 1 + this.k] = this.position[i + this.k];
//保存新的比较值,this.rank[i]为下一次计数排序时第i个被排序值对应的比较值
newRank[c[this.rank[i + this.k]] - 1 + this.k] = this.rank[i];
//出现次数减1,以便下一次出现相同被比较值时放在前面一个位置
c[this.rank[i + this.k]]--;
}

//更新position为已排序值
System.arraycopy(b, this.k, this.position, this.k, this.length - this.k);


//==========开始第二次计数排序=============
//初始化比较值出现的次数
Arrays.fill(c, 0);
//统计比较值出现的次数
for (int i = 0; i < this.length; i++)

{
c[newRank[i]]++;
}
//统计小于等于某个数的比较值出现次数
for (int i = 1; i < this.length; i++)

{
c[i] = c[i] + c[i - 1];
}
//依次放置被排序值
for (int i = this.length - 1; i >= 0; i--)

{
b[c[newRank[i]] - 1] = this.position[i];
c[newRank[i]]--;
}
//更新位置数组
this.position = b;
}

//O(N*logN)
//计算1前缀名次数组
private void computeRank_1()

{
//构造1前缀后缀数组,然后排序
charWithSatelliteData[] suffixArray = new charWithSatelliteData[this.length];
for (int i = 0; i < this.length; i++)

{
suffixArray[i] = new charWithSatelliteData(this.datas[i], i);
}
Arrays.sort(suffixArray);
//计算名次,为了使得相同的字符拥有相同的名次,所以应用 _rank
int _rank = 0;//目前出现的名次
for (int i = 0; i < this.length; i++)

{
//遇到新字符时更新名次
if (i > 0 && suffixArray[i].c != suffixArray[i - 1].c)

{
_rank = i;
}
this.rank[suffixArray[i].position] = _rank;
}
}
//O(N)
//用位置数组与名次数组计算新的名次数组
private void computeRank_2kByPos_2kAndRank_k()

{
//为了让相同的后缀数组拥有相同的名次,所以使用_rank,使用方法类似computeRank_1()
int _rank = 0;//目前出现的名次
int[] newRank = new int[this.length];

for (int i = 0; i < this.length; i++)

{
//if suffix(position[i]) != suffix(position[i-1]) and suffix(position[i]+k) != suffix(position[i-1]+k)
//equal to rank[position[i]] != rank[position[i-1]] and rank[position[i]+k] != rank[position[i-1]+k]
//where position[i-1] + k > length , suffix(position[i-1]) is distinct
if (
(i > 0)
&& (
(this.rank[this.position[i]] != this.rank[this.position[i - 1]])
|| (this.position[i - 1] + this.k >= this.length)
|| (this.rank[this.position[i] + this.k] != this.rank[this.position[i - 1] + this.k])
)
)

{
_rank = i;
}
newRank[this.position[i]] = _rank;
}
this.rank = newRank;
}

//O(N)
//用1前缀名次数组计算1前缀位置数组
//这个方法也可以用k前缀名次数组计算k前缀位置数组
private void computePos_1ByRank_1()

{
//因为相同的名次要分配不同的位置,所以使用rankCount[]
int rankCount[] = new int[this.length];
Arrays.fill(rankCount, 0);
for (int i = 0; i < this.length; i++)

{
this.position[this.rank[i] + rankCount[this.rank[i]]] = i;
rankCount[this.rank[i]]++;
}
}

//call after computePosAndRank()
private void computeHeight()

{
int[] h = new int[this.length];//h[i]表示suffix(i)与比它小一个名次的后缀字符串的lcp
for (int i = 0; i < this.length; i++)

{
if (this.rank[i] == 0)

{
h[i] = 0;
}
else if ((i == 0) || (h[i-1] <= 1))

{
h[i] = getSameLength(i, this.position[this.rank[i] - 1]);
}
else

{
h[i] = h[i - 1] - 1 + getSameLength(i + h[i - 1] - 1, this.position[this.rank[i] - 1] + h[i - 1] - 1);
}
}

int[] tmpHeight = new int[this.length];
for (int i = 0; i < this.length; i++)

{
tmpHeight[i] = h[this.position[i]];
}

this.height = new RMQ(tmpHeight);
}

//通过逐渐比较获得suffix(firstIndex)与suffix(secondIndex)的lcp
private int getSameLength(int firstIndex, int secondIndex)

{
int sameLength = 0;
while ((firstIndex < this.length) && (secondIndex < this.length) && (this.datas[firstIndex] == this.datas[secondIndex]))

{
sameLength++;
firstIndex++;
secondIndex++;
}
return sameLength;
}

}



//此class用于计算1前缀名次数组时的排序,c为后缀,position为后缀的位置
class charWithSatelliteData implements Comparable


{
public char c;
public int position;

public charWithSatelliteData(char c, int position)

{
this.c = c;
this.position = position;
}

public int compareTo(Object arg0)

{
charWithSatelliteData arg = (charWithSatelliteData)arg0;
if (this.c > arg.c)

{
return 1;
}
else if (this.c == arg.c)

{
return 0;
}
else

{
return -1;
}
}
}


class RMQ


{
private int[] datas;
private int[][] m;
private int n, log_n;

public RMQ(int[] datas)

{
this.datas = datas;
this.n = datas.length;
for (this.log_n = 0; 1 << this.log_n <= n; this.log_n++) ;

this.m = new int[this.n][this . log_n];
for (int i = 0; i < this.n; i++)

{
m[i][0] = this.datas[i];
}
for (int j = 1; j < this.log_n; j++)

{
int power_j = 1 << j;
int power_jMinus1 = 1 << (j - 1);
for (int i = 0; i + power_j - 1 < this.n; i++)

{
if (this.m[i][j - 1] < this.m[i + power_jMinus1][j - 1])

{
this.m[i][j] = this.m[i][j - 1];
}
else

{
this.m[i][j] = this.m[i + power_jMinus1][j - 1];
}
}
}
}

public int getMinimum(int stratIndex, int endIndex)

{
//TODO 改为swap
if (stratIndex > endIndex)

{
int tmp = stratIndex;
stratIndex = endIndex;
endIndex = tmp;
}

int k = (int)(Math.log(endIndex - stratIndex + 1) / Math.log(2));

if (this.m[stratIndex][k] <= this.m[endIndex - (1 << k) + 1][k])

{
return this.m[stratIndex][k];
}
else

{
return this.m[endIndex - (1 << k) + 1][k];
}
}

public int getData(int index)

{
return this.datas[index];
}

public int length()

{
return this.datas.length;
}
}