bzoj 3676: [Apio2014]回文串 manachar+后缀自动机+倍增(回文树)

17 篇文章 0 订阅
7 篇文章 0 订阅

题意

考虑一个只包含小写字母的字符串s。我们定义s的一个子串t的“出 现值”为t在s中的出现次数乘以t的长度。请你求出s的所有回文子串中的最大出现值。
n<=300000

分析

因为自己对马拉车的性质不太熟悉,所以一开始没做出来。
考虑在跑马拉车算法的时候,所有本质不同的回文串必然包含在所有能使mx增加的回文串内,也就是最多只有O(n)个。那么我们只要求出后缀自动机,然后每找到一个回文串就扔进后缀自动机里跑一下就好了。
但是由于找祖先太慢,我们可以先预处理,然后倍增找即可。

后来才发现这是回文树的裸题。然后就去学习了一发。回文树介绍

代码

后缀自动机

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

typedef long long LL;

const int N=300005;
const int M=N*2;

int n,pos[N],mx[M],c[M],b[N],f[M],fa[M][19],ch[M][26],last,cnt,l[N*2];
char a[N*2],s[N];
LL ans;

void ins(int x,int id)
{
    int p,q,np,nq;
    p=last;last=np=++cnt;mx[np]=mx[p]+1;f[np]=1;pos[id]=np;
    for (;!ch[p][x]&&p;p=fa[p][0]) ch[p][x]=np;
    if (!p) fa[np][0]=1;
    else
    {
        q=ch[p][x];
        if (mx[q]==mx[p]+1) fa[np][0]=q;
        else
        {
            nq=++cnt;mx[nq]=mx[p]+1;
            memcpy(ch[nq],ch[q],sizeof(ch[q]));
            fa[nq][0]=fa[q][0];
            fa[q][0]=fa[np][0]=nq;
            for (;ch[p][x]==q;p=fa[p][0]) ch[p][x]=nq;
        }
    }
}

void prework()
{
    for (int i=1;i<=cnt;i++) b[mx[i]]++;
    for (int i=1;i<=n;i++) b[i]+=b[i-1];
    for (int i=1;i<=cnt;i++) c[b[mx[i]]--]=i;
    for (int i=cnt;i>=1;i--) f[fa[c[i]][0]]+=f[c[i]];
    for (int i=1;i<=cnt;i++)
        for (int j=1;j<=18;j++) fa[c[i]][j]=fa[fa[c[i]][j-1]][j-1];
}

void query(int l,int r)
{
    if (a[l]=='#') return;
    l/=2;r/=2;
    int now=pos[r];
    for (int i=18;i>=0;i--) if (mx[fa[now][i]]>=r-l+1) now=fa[now][i];
    ans=max(ans,(LL)(r-l+1)*f[now]);
}

void manachar()
{
    for (int i=1;i<=n*2+1;i++)
        if (i%2==1) a[i]='#';
        else a[i]=s[i/2];
    int mx=0,id;
    for (int i=1;i<=n*2+1;i++)
    {
        if (i<=mx) l[i]=min(mx-i+1,l[id*2-i]);
        while (i+l[i]<=n*2+1&&i+l[i]>=1&&a[i+l[i]]==a[i-l[i]])
        {
            l[i]++;
            if (i+l[i]-1>mx) mx=i+l[i]-1,id=i,query(i-l[i]+1,i+l[i]-1);
        }
    }
}

int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    cnt=last=1;
    for (int i=1;i<=n;i++) ins(s[i]-'a',i);
    prework();
    manachar();
    printf("%lld",ans);
    return 0;
}

回文树

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N=300005;

int n;
char s[N];

struct pam
{
    int ch[N][26],size[N],fail[N],len[N],last,sz;
    pam()
    {
        len[1]=-1;fail[0]=fail[1]=1;sz=1;
    }
    void ins(int c,int n)
    {
        int p=last;
        while (s[n-1-len[p]]!=s[n]) p=fail[p];
        if (!ch[p][c])
        {
            int now=++sz,k=fail[p];len[now]=len[p]+2;
            while (s[n-1-len[k]]!=s[n]) k=fail[k];
            fail[now]=ch[k][c];ch[p][c]=now;
        }
        last=ch[p][c];size[last]++;
    }
    void solve()
    {
        LL ans=0;
        for (int i=sz;i>=1;i--) size[fail[i]]+=size[i],ans=max(ans,(LL)size[i]*len[i]);
        printf("%lld",ans);
    }
}pam;

int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    for (int i=1;i<=n;i++) pam.ins(s[i]-'a',i);
    pam.solve();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值