[APIO2014]回文串

洛谷  P3649 [APIO2014]回文串

https://www.luogu.org/problem/show?pid=3649

题目描述

给你一个由小写拉丁字母组成的字符串 。我们定义  的一个子串的存在值为这个子串在  中出现的次数乘以这个子串的长度。

对于给你的这个字符串 ,求所有回文子串中的最大存在值。

输入输出格式

输入格式:

 

一行,一个由小写拉丁字母(a~z)组成的非空字符串 

 

输出格式:

 

输出一个整数,表示所有回文子串中的最大存在值。

 

输入输出样例

输入样例#1:
abacaba
输出样例#1:
7
输入样例#2:
www
输出样例#2:
4

说明

【样例解释1】

用  表示字符串  的长度。

一个字符串  的子串是一个非空字符串 ,其中 。每个字符串都是自己的子串。

一个字符串被称作回文串当且仅当这个字符串从左往右读和从右往左读都是相同的。

这个样例中,有  个回文子串 a,b,c,aba,aca,bacab,abacaba。他们的存在值分别为 

所以回文子串中最大的存在值为 

第一个子任务共 8 分,满足 

第二个子任务共 15 分,满足 

第三个子任务共 24 分,满足 

第四个子任务共 26 分,满足 

第五个子任务共 27 分,满足 

 

后缀数组+manacher+RMQ

1、后缀数组得到height数组后,对height数组做一个RMQ的预处理

2、用manacher算法找出回文串。利用性质:设pos为当前回文半径覆盖的最右端,只有使pos变大的回文串,才有可能产生新的答案。因为其他的的都在  关于pos的回文中心 对称的位置计 算过了。所以只要pos变大,就计算最大存在值

3、最大存在值=出现次数*长度,如何找次数?用后缀数组

     在height数组里找到以回文串为前缀的位置,向上二分,向下二分,在height数组中锁定相邻的height值都>=回文串长度的区间,这个区间的大小就是回文串的出现次数

注:本代码在bzoj上 Wrong Answer ,原因还没找着,洛谷上AC

学了一个优化:若频繁使用log值,比如RMQ里, int w=int(log(n)/log(2)+0.001)+1; 非常慢非常慢

所以提前预处理log  for(int i=2;i<=n;i++) lgg[i]=lgg[i>>1]+1;

#include<cstdio>
#include<cstring> 
#include<algorithm>
#define N 600110        
using namespace std;
char s[N],b[N];
int a[N],n,m,v[N],p,q=1,k,sa[2][N],rk[2][N],h[N],lgg[N];
int rad[N*2];
int f[N*2][21];
long long ans;
void mul(int *sa,int *rk,int *SA,int *RK)
{
    for(int i=1;i<=n;i++) v[rk[sa[i]]]=i;
    for(int i=n;i;i--) 
     if(sa[i]>k) SA[v[rk[sa[i]-k]]--]=sa[i]-k;
    for(int i=n-k+1;i<=n;i++) SA[v[rk[i]]--]=i;
    for(int i=1;i<=n;i++) RK[SA[i]]=RK[SA[i-1]]+(rk[SA[i-1]]!=rk[SA[i]]||rk[SA[i-1]+k]!=rk[SA[i]+k]);
}
void presa()
{
    for(int i=1;i<=n;i++) v[a[i]]++;
    for(int i=1;i<=26;i++) v[i]+=v[i-1];
    for(int i=1;i<=n;i++) sa[p][v[a[i]]--]=i;
    for(int i=1;i<=n;i++) rk[p][sa[p][i]]=rk[p][sa[p][i-1]]+(a[sa[p][i]]!=a[sa[p][i-1]]);
    for(k=1;k<n;k<<=1,swap(p,q)) 
    {
        mul(sa[p],rk[p],sa[q],rk[q]);
        if(rk[q][sa[q][n]]==n) 
         {swap(p,q);return;}
    }

}
void getheight()
{
    int j,g=0;
    for(int i=1;i<=n;i++)
    {
        j=sa[p][rk[p][i]-1];
        while(a[j+g]==a[i+g]) g++;
        h[rk[p][i]]=g; if(g) g--;
    }
}
void rmq()
{
    for(int i=1;i<=n;i++) f[i][0]=h[i];
    for(int i=1,j=2;j<=n;i++,j*=2)
     for(int g=1;g<=n-j+1;g++)
      f[g][i]=min(f[g][i-1],f[g+j/2][i-1]);
}
bool check(int l,int r,int len)
{
    if(l>r) return false;
    int y=lgg[r-l+1];
    int g=min(f[l][y],f[r-(1<<y)+1][y]);
    if(g>=len) return true;
    return false;
}
void solve(int x,int y)
{
    x=(x+1)/2;y/=2;
    int len=y-x+1,start=rk[p][x];
    int l,r,mid,up,down;
    l=1,r=start,up=start;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(check(mid+1,start,len)) r=mid-1,up=mid;
        else l=mid+1;
    }
    l=start,r=n;down=start;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(check(start+1,mid,len)) l=mid+1,down=mid;
        else r=mid-1;
    }
    ans=max(1ll*(down-up+1)*len,ans);
} 
void manacher()
{
    b[m]='!';
    for(int i=1;i<=n;i++)
    {
        b[++m]='#';
        b[++m]=s[i];
    }
    b[++m]='#';
    b[m+1]='@';
    int pos=0,id=0,x=0;
    for(int i=1;i<=m;i++)
    {
        if(pos>i) x=min(rad[id*2-i],pos-i);
        else x=1;
        while(b[i-x]==b[i+x]) 
        {
            if(i+x>pos) solve(i-x,i+x);
            x++;
        }
        if(i+x>pos) pos=i+x,id=i;
        rad[i]=x;
    }
}
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=2;i<=n;i++) lgg[i]=lgg[i>>1]+1;
    for(int i=1;i<=n;i++) a[i]=s[i]-'a'+1;
    presa();
    getheight();
    rmq();
    manacher();
    printf("%lld",ans);
}

好几个错误:

① manacher里是只要更新了最右端就计算,这里的更新不是已确定的最终结果,更新过程也要算,所以while里只要i+x>mid,就计算

② 回文串在height数组里锁定位置,设回文串起始位置为x,那么height中位置为(x+1)/2,无论这个位置是字符还是‘#’。

   自己写的时候,写了个这个:

if(b[i]=='#') return;
int len=rad[i]-1,top=i-len+1>>1,start=rk[p][top];

i是回文中心

好像是不对

③ 二分时,若想检验锁定的区间[l,r],check里应为[l+1,r],因为height[i]表示的是i和i的上一个的lcp

   一开始没想到,不过后来想到了

转载于:https://www.cnblogs.com/TheRoadToTheGold/p/6596715.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值