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); }
解法二:
原串和其反串拼接,中间用两个不一样的字符隔开
然后构建回文自动机
考虑一个双倍回文的分割点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\n",pos[i],pos[m-i]); ans=max(ans,len[use[lca]]<<1); //printf("%d\n",ans); } printf("%d",ans); } int main() { scanf("%d",&n); scanf("%s",ss+1); build(); solve(); }