【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);
}

 

 

 

 

posted @ 2019-11-05 12:07  散樗  阅读(178)  评论(0编辑  收藏  举报