HDU-5371 Hotaru‘s problem

题目大意: 给定一个数字串,找出其中最长的一个符合要求的数字串,要求数字串中第一部分和第三部分相同,第一部分和第二部分相反。

思路:首先看到第一部分和第二部分相反,可以联系到回文子串,本题是基于manacher算法上分析的,如果你还不懂manacher算法可以先去看下我之前的另一篇文章,有点长,只不过很详细。

 manacher详解

好了,现在我们已经知道,第一部分加上第二部分是一个回文子串,然后题目又说第三部分等于第一部分,既然相等,我们可以知道第三部分和第二部分也是回文子串,我们姑且先把第一部分和第二部分合称“现回文子串”(为了解释方便),回文子串是中心对称,按理说应该可以是奇数,也可以是偶数,但是这道题具有特殊性,注意读题,题目说第一部分和第二部分和第三部分是连续的子串,就这说明在第一部分和第二部分中没有任何其他符号,偶+偶 == 偶,奇+奇 == 偶,所以“现回文子串”一定是偶数,

因为上文已经判断了“现回文子串”一定是偶数,所以,可以得知第一部分的长度和第二部分的长度是相等的,因为第三部分和第一部分相等,所以可以得出整个子串,是由三组长度相同的连续子串构成的。

因为通过manacher算法,我们可以得出p[]数组,也就是得到以该点为中心的最长回文子串,现在我们得到了这些回文子串,就相当于得到了许多的“现回文子串”(也就是第一部分+第二部分)。现在目的就很明确了,就是要找到第三部分。

因为第三部分和第一、第二部分大小相同,所以假设目前的点为 i 我们只要找到 i +p[i],如果它是的话,它的p[ ]应该大于或者等于p[i],看图看图。

 所以说,只要p[i +p[i]] >= p[i],就成立了,就不用进入下图的while循环。大于的部分不用管,达到我们的要求就可以了。

如果没达到呢,我们 可以把p[ i ]里面的都试一下(如果p[i]范围太小就直接退出就可以了,看看代码的if和循环判断的条件),代码实现就是下面这一步了。

                int tlen = p[i];
                while(tlen > sum && p[i + tlen] < tlen)
                    tlen--;

细节:现在就到了运用刚刚花了点时间,讲“现回文子串”一定是偶数串的原因了,因为一定是偶数所以我们开始遍历,并且只i += 2,因为这样遍历到的,对应修饰后的数组都是0(用来修饰无意义的,如果不知道,请看上文发的manacher的讲解)不这样节省时间就会超时。

        for(int i = 3;i < len;i+= 2){//从分析可知,该子串一定是偶数,所以从第二个偶数开始,并且只看偶数就可以了。
            //补充一下i++的话可能会超时哦
            if(p[i] > sum){//如果当前点的最大子串不如之前,直接跳过即可
                int tlen = p[i];
                while(tlen > sum && p[i + tlen] < tlen)
                    tlen--;
                sum = max(sum,tlen);
            }
        }

代码实现:

#include<stdio.h>
#include<algorithm>
using namespace std;
int arr[100005];
int t[200010];
int p[200010];
int main(){
    int T;
    scanf("%d",&T);
    for(int iu = 1; iu <= T;iu++){
        int num;
        scanf("%d",&num);
        for(int i = 0; i < num;i++)
            scanf("%d",&arr[i]);

        //处理字符串
        t[0] = -1;//独一无二的头
        t[1] = 0;
        int len = 2;
        for(int i = 0; i < num;i++){
            t[len++] = arr[i];
            t[len++] = 0;
        }
        t[len] = -2;//独一无二的尾

        //求出p[i]
        int center = 0;
        int max_right = 0;
        for(int i = 1; i < len;i++){
            int j = center * 2 - i;
            if(i + p[j] <= max_right)
                p[i] = p[j];
            else
                p[i] = 0;

            while(t[i - p[i] - 1] == t[i + p[i] + 1])
                p[i]++;

            if(i + p[i] > max_right){
                center = i;
                max_right = i + p[i];
            }

        }

        //本题的关键
        int sum = 0;
        for(int i = 3;i < len;i+= 2){//从分析可知,该子串一定是偶数,所以从第二个偶数开始,并且只看偶数就可以了。
            //补充一下i++的话可能会超时哦
            if(p[i] > sum){//如果当前点的最大子串不如之前,直接跳过即可
                int tlen = p[i];
                while(tlen > sum && p[i + tlen] < tlen)
                    tlen--;
                sum = max(sum,tlen);
            }
        }
        for(int i = 0; i <= len;i++)
            printf("%d " ,p[i]);
        printf("Case #%d: %d\n",iu,sum / 2 * 3);
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值