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