Contest 7 1003 Hotaru's problem【字符串】

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5371

这题出的还是蛮卡时间的。
三段中第一段与第二段回文、与第三段相同。可以想到是一二段回文紧接着二三段也是回文。那么问题归于高效的找到回文串。

这里学到一种算法:Manacher。时间复杂度O(N)
回文串很麻烦的一个地方就是有分奇偶的情况。而这个算法巧妙地统一起来考虑了。还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配。
主要过程是如下:
先在每两个相邻字符中间插入一个分隔符(当然这个分隔符要在原串中没有出现过,一般可以用‘#‘)。这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了。然后用一个辅助数组P记录以每个字符为中心的最长回文串的信息。P[id]记录的是以字符str[id]为中心的最长回文串,当以str[id]为第一个字符,这个最长回文串向右延伸了P[id]个字符。
例:
这里写图片描述
这里有一个很好的性质,P[id]-1就是该回文子串在原串中的长度。

现在的关键问题就在于怎么在O(n)时间复杂度内求出P数组了。
用id表示最大回文子串中心的位置,mx则为id+P[id],也就是最大回文子串的边界。如果mx > i,那么P[i] >= MIN(P[2 * id - i], mx - i)。我看到这个式子不大理解,有个复杂点的版本看起来逻辑很多:

//记j = 2 * id - i,也就是说 j 是 i 关于 id 的对称点。
if (mx - i > P[j]) 
    P[i] = P[j];
else /* P[j] >= mx - i */
    P[i] = mx - i; // P[i] >= mx - i,取最小值,之后再匹配更新。

当 mx - i > P[j] 的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于 i 和 j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有 P[i] = P[j]。
当 P[j] > mx - i 的时候,以S[j]为中心的回文子串不完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 P[i] >= mx - i。至于mx之后的部分是否对称,匹配看咯。
根据以上结论可线性从前往后扫一遍。用mx记在i之前的回文串中,延伸至最右端的位置,同时用id这个变量记下取得这个最优mx时的id值。
为了防止字符比较的时候越界,往往在串最前面还会加一个特殊字符。

好吧说了这么多都是介绍这个Manacher算法。
而这一个题是把这个算法作为一个工具套用,增加效率的。读入的都是正整数,所以我们可以插入-1替代’#‘,防越界的标识符用-2。这样处理好后用Manacher算法就可以得到每一位上最长延展的回文串长度。
而我们需要的是三段式的,所以只要找到两个-1的回文点正好凑出符合要求的三段就好了。
这里我们遍历每个-1点,设位置是i,后面与之对应的-1点的位置最长够到i+p[i]-1,从该位置往回缩每个-1点都有可能符合,符合的情况就是p[j]>=j(j即是距离,最长p[i],依次缩减)。
但是我第一遍交的时候依然T了。。是一个小细节上被卡了一下,要再减个枝。因为max是不断更新的,而每次遍历的i都要从最远的距离往回找对应的-1点,其实会有很多不必要的操作。如果此时长度已经比max小了,即使找到也不用更新了,那么不必做这些浪费时间的工作了。减掉这个就可以A了。

#include<iostream>
#include<cstdio>
using namespace std;
int s[300000],str[300000],p[300000];
int T,TT,n,ans;
void kp()
{
    int i,mx=0,id=0;
    for(i=1;i<n;i++)
    {
        if(mx>i) p[i]=min(p[2*id-i],p[id]+id-i);
        else p[i]=1;
        while(str[i+p[i]]==str[i-p[i]]) p[i]++;
        if(p[i]+i>mx)
        {
            mx=p[i]+i;id=i;
        }
    }
}
void init()
{
    int i;
    str[0]=-2;str[1]=-1;
    for(i=0;i<n;i++)
    {
        str[i*2+2]=s[i];
        str[i*2+3]=-1;
    }
    n=n*2+2;str[n]=-2;
}
int main()
{
    int i,j;
    scanf("%d",&T);TT=1;
    while(T--)
    {
        scanf("%d",&n);
        for(i=0;i<n;i++) scanf("%d",&s[i]);
        init();
        kp();
        ans=0;
        for(i=0;i<n;i++)
            if(str[i]==-1)
        {
            j=p[i];
            while(j>0&&i+j-1>=n) j=j-2;
            while(j>0)
            {
                if(p[i+j-1]>=j)
                {
                    if(j>ans) ans=j;
                   // printf("%d %d %d\n",i,j,ans);
                    break;
                }
                j=j-2;
                if(j<ans) break;
            }
        }
        printf("Case #%d: %d\n",TT,(ans-1)/2*3);TT++;
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章