BZOJ4248 JOI15 AAQQZ

題目大意

給定一個字串集爲[1,C] 的長度爲N 的字符串S ,你可以選擇一個區間[L,R] ,將[L,R] 中的字符從小到大排序,接着你得到的權值就是當前S 中的最長迴文串的長度。問你最多能得到多大權值。

數據範圍

1C,N3000

題解

這道題感覺非常的好玩,不知道爲什麼要強行說是一個分類討論題。。。
首先我們將最終得到的最長迴文串的迴文中心進行分類討論:
1. 迴文中心沒有被排序
2. 迴文中心是排序後調過來的,並且存在某個前綴,使得其沒有被排序
注意上面的分類沒有考慮到一種迴文串整個都是被排序了得情況,我們可以最後將S 排序,統計一下即可。

接下來我們考慮排序的區間都是在右邊的,也就是不會對迴文串的前面排序,對於前面排序的情況我們可以整個串取反來做。

迴文中心沒有被排序

先枚舉迴文中心,設爲位置ii ,對於偶迴文的情況是類似的。不妨想一下,我們一開始必然是先儘量讓他往兩邊先匹配,對於多出來的用排序來匹配。設當前極長的迴文串爲(L_r,R_l)(Lr,Rl) ,枚舉排序區間的右端點爲[R_r][Rr] ,設左邊能匹配到[x,L_r][x,Lr] ,那麼由於我們要對[R_l,R_r][Rl,Rr] 排序,所以[x,Lr] 必然是單調遞減的。因此,我們一開始可以先暴力地找到最小的Ll ,使得[Ll,Lr] 爲單調遞減的,那麼x 必然在這個區間內。現在的關鍵就是對於一個[Rl,Rr] ,如何確定其對應的最小的x
Cntc,l,r 表示c 字符在[l,r] 出現的次數,那麼我們必須要滿足iCnti,x,LrCnti,Rl,Rr ,不然我們就不可能湊出來了。但還有一個條件不能忽視,就是你中間不能斷開,也就是要滿足i<SxCnti,x,Lr=Cnti,Rl,Rr ,相當於不能有i<Sx ,使得Cnti,x,Lr<Cnti,Rl,Rr

那麼我們相當於可以設兩個指針l1,l2 ,前面表示最小的x 滿足iCnti,x,LrCnti,Rl,Rr ,後者表示最小的x 滿足不存在i<Sx ,使得Cnti,x,Lr<Cnti,Rl,Rr ,那麼考慮Rr 向後移動一位,相當於CntSRr 加一,那麼我們將l1 向前移,將l2 向後移即可。
但有一個值得注意的小細節,就是假如當前Lrx=RrRl ,也就是我們最終可以再在外面匹配,那麼我們可以預處理出Fi,j 表示以i 爲結尾,j 爲開頭的兩個串最多匹配多少的長度既可。

這裏的複雜度爲O(N2)

迴文中心被排序

設最大的沒有被排序的位置爲Lr ,那麼其接下來的字符必然是從Lr 開始,第一個比SLr 小的字符,可以畫個圖感受一下。並且迴文中心也必然是這個字符,因爲他要對稱過去。接下來的做法與迴文中心沒有被排序基本相同。唯一的細節就是假如當前擴展到了Rr ,並且SRr 比迴文中心要小的話,我們就不能繼續匹配下去了,因爲這是不合法的,假如相等,我們可以直接將他塞到中間即可。

關鍵還是代碼吧。。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int MAXN = 3005;

int F[MAXN][MAXN],S[MAXN],N,C;

void Get_F()
{
    memset(F,0,sizeof F);
    //F[i][j] max(S[i - l + 1..i] = S[j..j + l - 1])
    for(int i = 1;i <= N;i ++)
        for(int j = N;j >= i;j --)
            if (F[i - 1][j + 1] >= 0 && S[i] == S[j]) F[i][j] = F[i - 1][j + 1] + 1;
}

int TreatR(int lr,int cent,int rl)
{
    if (!lr || rl > N) return 0;
    static int Cnt[MAXN],Cur[MAXN],Stack[MAXN][2];
    int top = 0;
    memset(Cnt,0,sizeof Cnt),memset(Cur,0,sizeof Cur);
    for(int u = lr;u;u --)
    {
        ++ Cnt[S[u]];
        if (S[u] >= Stack[top][0]) Stack[++ top][0] = S[u],Stack[top][1] = Cnt[S[u]]; else
            break;
    }
    int MiLen = 0,NeedLen = 0,LimLen = top,tmp = 0;
    for(int rr = rl;rr <= N;rr ++)
    {
        if (S[rr] < cent) break; else
            if (S[rr] == cent) ++ MiLen; else
            {
                ++ Cur[S[rr]];
                if (Cnt[S[rr]] >= Cur[S[rr]])
                {
                    while (NeedLen + 1 <= top)
                    {
                        int val = Stack[NeedLen + 1][0];
                        if (Cur[val] >= Stack[NeedLen + 1][1]) ++ NeedLen; else break;
                    }
                } else
                {
                    while (LimLen)
                    {
                        int val = Stack[LimLen][0];
                        if (val > S[rr]) -- LimLen; else break;
                    }
                }
            }
        int cl = min(NeedLen,LimLen);
        tmp = max(tmp,cl * 2 + MiLen);
        if (rr - rl + 1 == cl + MiLen) tmp = max(tmp,cl * 2 + MiLen + 2 * F[lr - cl][rl + cl + MiLen]);
    }
    return tmp;
}

int Calc()
{
    Get_F();
    //calc middle as a position
    int tmp = 0;
    for(int i = 1;i <= N;i ++)
    {
        int lr = i,rl = i,d = F[i][i];
        lr -= d,rl += d;
        tmp = max(tmp,TreatR(lr,-1,rl) + 2 * d - 1);
    }
    //calc middle as a middle
    for(int i = 1;i < N;i ++)
    {
        int lr = i,rl = i + 1,d = F[i][i + 1];
        lr -= d,rl += d;
        tmp = max(tmp,TreatR(lr,-1,rl) + 2 * d);
    }
    //calc those when the sorted make difference
    for(int i = 1;i <= N;i ++)
    {
        int cent = -1;
        for(int j = i + 1;j <= N;j ++)
            if (S[j] < S[i]) {cent = S[j];break;}
        tmp = max(tmp,TreatR(i,cent,i + 1));
    }
    return tmp;
}

void Work()
{
    static int Bak[MAXN];
    for(int i = 1;i <= N;i ++) scanf("%d", &S[i]);
    int ans = Calc();
    memcpy(Bak,S,sizeof Bak);
    for(int i = 1;i <= N;i ++) S[i] = C - S[i] + 1;
    reverse(S + 1,S + N + 1);
    ans = max(ans,Calc());
    memcpy(Bak,S,sizeof Bak);
    int c = 0,cr = 0;
    sort(S + 1,S + N + 1);
    for(int i = 1;i <= N;i ++)
        if (S[i] != S[i - 1]) ans = max(ans,cr),cr = 1; else ++ cr;
    ans = max(ans,cr);
    printf("%d\n", ans);
}

int main()
{
    while (scanf("%d%d", &N, &C) != EOF) Work();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章