【CSP膜你赛】柠檬的密码(manacher 二分 单调性 st表)
题目描述
Lemon觉得他需要一个复杂的密码来保证他的帐号的安全。他经过多日思考,决定使用一个长度为奇数的回文串来作为他的密码。
但是这个回文串太长了,Lemon记不住,于是Lemon决定把它记在本子上。当然直接把密码明文记录实在太愚蠢了,于是Lemon决定在记录时加入一些无意义的字符以保证密码的安全。
具体来说,假设Lemon的密码串是S,Lemon选择了一个不超过len(S)/2的正整数x,然后把S的前x个字符组成的字符串设为Left,把S的后x个字符组成的字符串设为Right,把S其余的字符组成的字符串设为Mid.
Lemon实际记录在密码本上的内容是A+Left+B+Mid+C+Right. 其中A,B,C都是无意义的字符串(有可能是空串)。他觉得这样就很安全了。
某一天,Melon无意发现了Lemon的笔记本,并发现了这个字符串。Melon决定把Lemon的密码破解出来。但是显然有不计其数的可能密码。
Melon认为,Lemon的密码一定很长,于是他想知道,这个字符串里隐藏的最长可能的密码有多长呢?
输入格式
输入数据第一行包含一个正整数N,表示字符串的长度。
数据数据第二行包含一个长度为N的字符串,仅由小写字母组成,表示需要破译的字符串。
输出格式
输出数据仅包含一个整数,表示最长可能的密码的长度。
输入样例
25
orzabcdxyzefgfeqwertydcba
输出样例
13
提示
最长的可能的密码是abcdefgfedcba,长度为13
Lemon选择的x=4
Left="abcd" Right="dcba" Mid="efgfe"
A="orz" B="xyz" C="qwerty"
时间限制1s
对于20%的数据,满足N<=20
对于40%的数据,满足N<=300
对于60%的数据,满足N<=2000
对于100%的数据,满足N<=100000
分析
在网上找了半天题解啥都没找到最后放弃了,去问的Master_Yi
由于Right的右端点一定是原序列中的右端点,所以我们可以去枚举Right的左端点,也可以说是Right的长度,然后用hash值去找与它对称的Left
用贪心的思想去找最左边的且与Right对称的 Left(这样可以给中间的mid留下足够的长度),从小到大枚举Left的左端点的坐标,可以发现它有单调性的
模糊感性证明
如果当前枚举的左端点形成的Left与Right不对称,之后的枚举新的Right的长度时,以这个点为左端点的Left肯定与Right不对称
所以最左边与Right对称的Left的左端点一定随着Right的长度单增(或者说不下降
于是就可以O(n)确定Left和Right,剩下中间的Mid,因为Mid一定是个回文串,可以用manacher预处理每个点的len数组,即以这个点为对称中心的最长子串长度
设中间剩下的区间为(l,r),二分一下Mid的半径,设为k,用st表检验(l+k,r-k)中是否有len数组的值大于等于k的,如果有就更新答案,然后继续搜索。
整了这么多东西,发现复杂度只有nlogn。。。。。。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
int n,ans,ha[100005],st[100005][20],bw[100005],lg[100005];char ch[100005];
int que(int l,int r){return l<=r?max(st[l][lg[r-l+1]],st[r-(1<<lg[r-l+1])+1][lg[r-l+1]]):0;}
int main()
{
scanf("%d%s",&n,ch+1);bw[0]=1;
for(int i=2;i<=n+1;i++)lg[i]=lg[i/2]+1;
for(int i=1,r=0,mid=0;i<=n;bw[i]=bw[i-1]*29,i++)
{
st[i][0]=r>i?min(st[mid*2-i][0],r-i):1;
while(ch[i-st[i][0]]==ch[i+st[i][0]])st[i][0]++;
if(r<i+st[i][0])r=i+st[i][0],mid=i;ha[i]=(ch[i]-'a'+1)+ha[i-1]*29;
}
for(int k=1;k<=19;k++)for(int i=1;i<=n;i++)st[i][k]=max(st[i][k-1],i+(1<<(k-1))<=n?st[i+(1<<(k-1))][k-1]:0);
for(int i=n,j=1,tmp=0;i>=1;i--)
{
tmp=ch[i]-'a'+1+tmp*29;
while((j<n-i+1||ha[j]-ha[j-(n-i+1)]*bw[n-i+1]!=tmp)&&j<=n)j++;
int l=0,r=(i-j)/2,mid,ret;
while(l<=r&&j<=i)
{
mid=(l+r)>>1;
if(que(j+mid,i-mid)>=mid)ret=mid,l=mid+1;
else r=mid-1;
}
if(j<=i)ans=max((n-i+1)*2+ret*2-1,ans);
}
printf("%d\n",ans);
}