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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章