HDU 5371 Hotaru's problem Manacher+Set

9 篇文章 0 订阅
6 篇文章 0 订阅

题目大意:给一个序列,求一个最长的子区间,满足一下条件

  • 区间长度为3的倍数
  • 将区间均分成三部分,第一部分等于第三部分,第二部分是第一部分镜像翻转
  • 例:2 3 4|4 3 2|2 3 4就是一个合法的序列

通过观察我们知道,题目中合法的序列实际上就是一个有重叠部分的双回文串(上例中以|表示回文中心),那么可以得出一个暴力的方法那就是,枚举回文中心,然后向左右延伸,延伸过程中遇到满足要求的回文中心就更新答案。时间复杂度是 O(n2) 的。

虽然上述算法并不能通过本题,但是它给我们提供了一个初步的方向,暴力的瓶颈在于求回文串长度和更新答案。对于求回文串最长长度,我们有时间复杂度为 O(n) Manacher算法,但即使我们求出了每个回文中心的最长回文串长度,我们还是需要枚举以求出答案,这个时候就需要巧妙地运用set来维护了。

我们可以先用Manacher求出每个回文中心的回文串长度(注意可能对答案有影响的回文中心只存在于序列的间隙中,即在Manacher算法中添加的特殊字符如#上的答案才对我们有用),接着我们把这些中心按回文串长度从大到小排序,然后把它们(回文中心的下标)一个一个地加到set中去。

每添加一个回文中心,我需要在set中查找一个在当前回文中心的回文串长度范围内,离中心最远(答案尽可能大)的下标,然后更新答案即可。这里就体现了排序的作用,因为后面添加的回文中心的回文串长度一定小于前面添加的回文中心的回文串长度,故找到的答案一定是合法的。

145093032015-08-1415:47:18Accepted5371608MS5456K1447 BG++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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值