给定一个字串集为 [1,C] 的长度为 N 的字符串 S ,你可以选择一个区间 [L,R] ,将 [L,R] 中的字符从小到大排序,接着你得到的权值就是当前 S 中的最长回文串的长度。问你最多能得到多大权值。
1≤C,N≤3000
这道题感觉非常的好玩,不知道为什么要强行说是一个分类讨论题。。。
首先我们将最终得到的最长回文串的回文中心进行分类讨论:
1. 回文中心没有被排序
2. 回文中心是排序后调过来的,并且存在某个前缀,使得其没有被排序
注意上面的分类没有考虑到一种回文串整个都是被排序了得情况,我们可以最后将 S 排序,统计一下即可。
接下来我们考虑排序的区间都是在右边的,也就是不会对回文串的前面排序,对于前面排序的情况我们可以整个串取反来做。
先枚举回文中心,设为位置i i ,对于偶回文的情况是类似的。不妨想一下,我们一开始必然是先尽量让他往两边先匹配,对于多出来的用排序来匹配。设当前极长的回文串为(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,Lr≤Cnti,Rl,Rr ,不然我们就不可能凑出来了。但还有一个条件不能忽视,就是你中间不能断开,也就是要满足 ∀i<SxCnti,x,Lr=Cnti,Rl,Rr ,相当于不能有 i<Sx ,使得 Cnti,x,Lr<Cnti,Rl,Rr 。
那么我们相当于可以设两个指针 l1,l2 ,前面表示最小的 x 满足 ∀iCnti,x,Lr≤Cnti,Rl,Rr ,后者表示最小的 x 满足不存在 i<Sx ,使得 Cnti,x,Lr<Cnti,Rl,Rr ,那么考虑 Rr 向后移动一位,相当于 CntSRr 加一,那么我们将 l1 向前移,将 l2 向后移即可。
但有一个值得注意的小细节,就是假如当前 Lr−x=Rr−Rl ,也就是我们最终可以再在外面匹配,那么我们可以预处理出 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;
}