题目大意:给一个序列,求一个最长的子区间,满足一下条件
- 区间长度为3的倍数
- 将区间均分成三部分,第一部分等于第三部分,第二部分是第一部分镜像翻转
- 例:2 3 4|4 3 2|2 3 4就是一个合法的序列
通过观察我们知道,题目中合法的序列实际上就是一个有重叠部分的双回文串(上例中以|表示回文中心),那么可以得出一个暴力的方法那就是,枚举回文中心,然后向左右延伸,延伸过程中遇到满足要求的回文中心就更新答案。时间复杂度是 O(n2) 的。
虽然上述算法并不能通过本题,但是它给我们提供了一个初步的方向,暴力的瓶颈在于求回文串长度和更新答案。对于求回文串最长长度,我们有时间复杂度为 O(n) Manacher算法,但即使我们求出了每个回文中心的最长回文串长度,我们还是需要枚举以求出答案,这个时候就需要巧妙地运用set来维护了。
我们可以先用Manacher求出每个回文中心的回文串长度(注意可能对答案有影响的回文中心只存在于序列的间隙中,即在Manacher算法中添加的特殊字符如#上的答案才对我们有用),接着我们把这些中心按回文串长度从大到小排序,然后把它们(回文中心的下标)一个一个地加到set中去。
每添加一个回文中心,我需要在set中查找一个在当前回文中心的回文串长度范围内,离中心最远(答案尽可能大)的下标,然后更新答案即可。这里就体现了排序的作用,因为后面添加的回文中心的回文串长度一定小于前面添加的回文中心的回文串长度,故找到的答案一定是合法的。
14509303 | 2015-08-14 | 15:47:18 | Accepted | 5371 | 608MS | 5456K | 1447 B | G++ | Kurama |
---|
#include <cstdlib>
#include <cstdio>
#include <set>
#include <cstring>
#include <string>
#include <algorithm>
#define MAXN 100001
using namespace std;
int n,num[MAXN*2],p[MAXN*2];
struct position{int man;int p;}pos[MAXN];
//保存回文中心信息,man为长度,p为下标
bool cmp(const position &a,const position &b)
{return a.man>b.man;}
void Manacher()
{
memset(pos,0,sizeof pos);
memset(p,0,sizeof p);
int mx=0,id=0;
for(int i=1;i<=2*n;i++){
if(mx>i)p[i]=min(p[2*id-i],(mx-i));
else p[i] = 1;
while(num[i-p[i]]==num[i+p[i]])p[i]++;
if(i+p[i]>mx){
mx=i+p[i];
id=i;
}
if(i%2==0)pos[i/2]=(position){p[i]/2,(i+1)/2};
//添加可能对答案产生影响的回文中心
}
sort(pos,pos+n,cmp);
//将回文中心按长度排序
}
void init(){
memset(num,-1,sizeof num);
scanf("%d",&n);
for(int i=0;i<n;i++)scanf("%d",&num[i*2+1]);
Manacher();
}
set<int>s;
set<int>::iterator it;
int solve(){
s.clear();
int ans=0;
for(int i=0;i<n;i++){
if(i!=0){
if(pos[i].man<=ans)break;
it=s.lower_bound(pos[i].p-pos[i].man);
if(it!=s.end() && pos[i].p>*it)
ans=max(ans,pos[i].p-*it);
//在当前回文中心前面找
it=s.upper_bound(pos[i].p+pos[i].man);
if(it!=s.begin()){
--it;
if(pos[i].p<*it)
ans=max(ans,*it-pos[i].p);
}
//在当前回文中心后面找
}
s.insert(pos[i].p);
}
return ans*3;
}
int main()
{
int t;
scanf("%d",&t);
for(int i=1;i<=t;i++){
init();
printf("Case #%d: %d\n",i,solve());
}
return 0;
}