bzoj千题计划306:bzoj2342: [Shoi2011]双倍回文 (回文自动机)

https://www.lydsy.com/JudgeOnline/problem.php?id=2342

解法一:

对原串构建回文自动机

抽离fail树,从根开始dfs

设len[x]表示节点x表示的最长回文子串长度

在fail树上,x到根节点的路径上的点表示的字符串包含了x代表的回文子串的所有回文后缀/前缀

所以若dfs到了x,若len[x]为偶数,标记len[x]*2,如果在x的子树中能找到len为len[x]*2的点,那么len[x]*2*2就可以用来更新答案

 
#include<cstdio>
#include<algorithm>
 
using namespace std;
 
#define N 500001
 
char ss[N+1];
int s[N+1];
 
int tot=1,last;
int len[N],fail[N],tr[N][26];
int p,c,np,t;
 
bool ok[N<<1];
int ans;
 
int front[N],to[N],nxt[N],cnt;
 
void add(int u,int v)
{
    to[++cnt]=v; nxt[cnt]=front[u]; front[u]=cnt;
}
 
void extend(int i)
{
    p=last; c=s[i];
    while(s[i-1-len[p]]!=c) p=fail[p];
    if(!tr[p][c])
    {
        np=++tot;
        len[np]=len[p]+2;
        t=fail[p];
        while(s[i-1-len[t]]!=c) t=fail[t];
        fail[np]=tr[t][c];
        add(fail[np],np);
        tr[p][c]=np;
    }
    else np=tr[p][c];
    last=np;
}
 
void dfs(int x)
{
    if(ok[len[x]]) ans=max(ans,len[x]);
    if(!(len[x]&1)) ok[len[x]<<1]=true;
    for(int i=front[x];i;i=nxt[i]) dfs(to[i]);
    if(!(len[x]&1)) ok[len[x]<<1]=false;
}
 
int main()
{
    int n;
    scanf("%d",&n);
    scanf("%s",ss+1);
    s[0]=-1;
    for(int i=1;i<=n;++i) s[i]=ss[i]-'a';
    fail[0]=1;
    len[1]=-1;
    for(int i=1;i<=n;++i) extend(i);
    dfs(0);
    printf("%d",ans);
}
View Code

解法二:

原串和其反串拼接,中间用两个不一样的字符隔开

然后构建回文自动机

考虑一个双倍回文的分割点i和i+1

i是前缀回文的结束位置

i+1是后缀回文的开始位置

设以i为结束位置的最长回文子串为s1,在回文自动机上的节点为a

设以i+1开始位置的最长回文子串为s2,在回文自动机上的节点为b

设前缀以i结束,后缀以i+1开始的双倍回文子串的一半为s,长度为L

那么现在有两个要求:

1、L为偶数

2、s是s1的后缀,s是s2的前缀,且s最长

对于要求2,因为开始原串和反串拼接构建了回文自动机,所以就是求a和b在fail树上的LCA

对于要求1,对每个点x记录fail树上 x的祖先中离它最近的长度为偶数的回文串即可

倍增求LCA会超时

不会tarjan求LCA(~~~~(>_<)~~~~)

树链剖分求LCA 会被卡空间

最后还是选了树剖。。。

回文自动机用了map存储,

注意回文自动机中有节点0,在树剖第二遍dfs的时候,重儿子初始化的编号不能是0

 

#include<map>
#include<cmath>
#include<cstdio>
#include<algorithm>
 
using namespace std;
 
#define N 1000001
 
int n,m;
char ss[N+1];
int s[N+1];
 
int tot=1,last;
int len[N],fail[N];
map<int,int>tr[N];
int p,c,np,t;
 
int use[N],pos[N];
 
int front[N],to[N],nxt[N],cnt;
 
int bl[N],dep[N],siz[N],fa[N];
 
void add(int u,int v)
{
    to[++cnt]=v; nxt[cnt]=front[u]; front[u]=cnt;
}
 
void extend(int i)
{
    p=last; c=s[i];
    while(s[i-1-len[p]]!=c) p=fail[p];
    if(!tr[p][c])
    {
        np=++tot;
        len[np]=len[p]+2;
        t=fail[p];
        while(s[i-1-len[t]]!=c) t=fail[t];
        fail[np]=tr[t][c];
        add(fail[np],np);
        use[np]=len[np]&1 ? use[fail[np]] : np;
        tr[p][c]=np;
    }
    else np=tr[p][c];
    last=np;
    pos[i]=np;
}
 
void build()
{
    s[0]=-1;
    for(int i=1;i<=n;++i) s[i]=ss[i]-'a';
    m=n;
    s[++m]=26; s[++m]=27;
    for(int i=n;i;--i) s[++m]=ss[i]-'a';
    fail[0]=1;
    len[1]=-1;
    for(int i=1;i<=m;++i) extend(i);
}
 
void dfs1(int x)
{
    siz[x]=1;
    for(int i=front[x];i;i=nxt[i])
    {
        dep[to[i]]=dep[x]+1;
        fa[to[i]]=x;
        dfs1(to[i]);
        siz[x]+=siz[to[i]];
    }
}
 
void dfs2(int x,int top)
{
    int y=-1;
    bl[x]=top;
    for(int i=front[x];i;i=nxt[i])
        if(y==-1 || siz[to[i]]>siz[y]) y=to[i];
    if(y==-1) return;
    dfs2(y,top);
    for(int i=front[x];i;i=nxt[i])
        if(to[i]!=y) dfs2(to[i],to[i]);
}
 
int get_lca(int u,int v)
{
    while(bl[u]!=bl[v])
    {
        if(dep[bl[u]]<dep[bl[v]]) swap(u,v);
        u=fa[bl[u]];
    }
    return dep[u]<dep[v] ? u : v;
}
 
void solve()
{
    add(1,0);
    dfs1(1);
    dfs2(1,1);
    int ans=0;
    int lca;
    for(int i=1;i<=n;++i)
    {
        lca=get_lca(pos[i],pos[m-i]);
    //  printf("%d %d
",pos[i],pos[m-i]);
        ans=max(ans,len[use[lca]]<<1);
        //printf("%d
",ans);
    }
    printf("%d",ans);
}
 
int main()
{
    scanf("%d",&n);
    scanf("%s",ss+1);
    build();
    solve();
}
View Code
原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/8687833.html