回文树
基础插入算法
- 增量构造法
- 假设已经构造出\(s\)的回文树,现在在末尾加一个\(c\),维护\(sc\)的回文树。
定理:以新加入的字符\(c\)为结尾的,未在\(s\)中出现过的回文子串最多只有一个,且为\(sc\)的最长回文后缀。
证明:对于两个\(sc\)的回文后缀\(p\)和\(q\),不妨设\(|p|<|q|\)。 那么\(p\)一定是\(q\)的回文前缀,显然在\(s\)中出现过,只保留后缀\(q\)。
- 我们现在要找新加的回文串,即在\(s\)的\(fail\)链上找到一个长度最大的节点\(t\),满足\(s[|s|-len_t]=c\),则\(sc\)的最长回文后缀为\(ctc\)。
- 如果这个点是新建的,需要找\(fail\)。 相当于在\(fail[t]\)对应\(fail\)链上找一个长度最长的节点\(v\)使得\(s[|s|-len_v]=c\)。
- 时间复杂度\(O(|s|\log \sum)\)或\(O(|s|)\),使用势能分析可证。
代码:
void insert(int i) {
int p=lst,x=w[i]-'a';
for(;w[i-len[p]-1]!=w[i];p=fail[p]) ;
if(!ch[p][x]) {
int np=++cnt; len[np]=len[p]+2;
int q=fail[p];
for(;w[i-len[q]-1]!=w[i];q=fail[q]);
fail[np]=ch[q][x], ch[p][x]=np;
}
lst=ch[p][x]; siz[lst]++;
}
不基于势能分析的插入算法
- 对于回文树的节点\(t\),维护\(quick[t][c]\),表示\(t\)的最长的满足前一个字符为\(c\)的回文后缀。
- 那么如果当前满足\(s[|s|-len_t]=c\),则最长回文后缀就是\(ctc\)。 否则一定是\(c\ str(quick[t][c])\ c\) 。
- 同理,新加入点的\(fail\)指向的回文串就是\(c\ str(quick[p][c])\ c\) (\(p\)为\(sc\)的最长回文后缀对应节点)
- 考虑如何维护每个节点的\(quick\),对于一个节点\(t\), \(t\)的\(quick\)与\(fail_t\)的\(quick\)没有什么差别。所包含的回文后缀只是多了一个\(fail_t\)而已,将\(fail_t\)的\(quick\)复制一遍给\(t\),再将\(quick[t][c]\)置成\(fail_t\)加上一个字符。
- 将每个\(quick\)可持久化, 插入时间复杂度为\(O(\log\sum)\)
代码:
void insert(int i) {
int p=lst,x=w[i]-'a';
if(w[i-len[p]-1]!=w[i]) p=qui[p][x];
if(!ch[p][x]) {
int np=++cnt; len[np]=len[p]+2;
fail[np]=ch[qui[p][x]][x];
memcpy(qui[np],qui[fail[np]],sizeof(qui[fail[np]]));
qui[np][w[i-len[fail[np]]]-'a']=fail[np];
ch[p][x]=np;
}
lst=ch[p][x], siz[lst]++;
}
for(i=0;i<26;i++) qui[1][i]=qui[0][i]=1;
或
void update(int l,int r,int x,int y,int &p,int q) {
p=++TOT; ls[p]=ls[q]; rs[p]=rs[q];
if(l==r) {num[p]=y; return ;}
int mid=(l+r)>>1;
if(x<=mid) update(l,mid,x,y,ls[p],ls[q]);
else update(mid+1,r,x,y,rs[p],rs[q]);
}
int query(int l,int r,int x,int p) {
if(l==r) return num[p];
int mid=(l+r)>>1;
if(x<=mid) return query(l,mid,x,ls[p]);
else return query(mid+1,r,x,rs[p]);
}
void insert(int l,int i) {
int p=lst,x=w[i];
if(w[i-len[p]-1]!=w[i]||i-len[p]-1<l) p=query(0,25,x,root[p]);
if(!ch[p][x]) {
int np=++cnt; len[np]=len[p]+2;
fail[np]=ch[query(0,25,x,root[p])][x];
update(0,25,w[i-len[fail[np]]],fail[np],root[np],root[fail[np]]);
ch[p][x]=np;
}
lst=ch[p][x];
}
for(i=0;i<26;i++) update(0,25,i,1,root[0],root[0]),update(0,25,i,1,root[1],root[1]);
## BZOJ4932: 基因
https://lydsy.com/JudgeOnline/problem.php?id=4932
分析:
- 分块,预处理出来每块到所有点的答案和最长前缀回文串。
- 这步操作需要不依赖于势能分析的插入算法。
- 求出\(mn[i][j]\)以\(i\)块开始出现回文串\(j\)的最近位置。
- 每次向前插入字符统计答案即可。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cmath>
using namespace std;
#define N 100050
#define M 350
int type,n,Q,bl[N],size,blo,L[M],R[M],mn[M][N];
int ans[M][N],cnt=1,len[N],ch[N][26],lst,lst2,fail[N],vis[N],tot,LST[M][N];
char w[N];
int ls[N*20],rs[N*20],root[N],num[N*20],TOT;
int qui[N][26];
void update(int l,int r,int x,int y,int &p,int q) {
p=++TOT; ls[p]=ls[q]; rs[p]=rs[q];
if(l==r) {num[p]=y; return ;}
int mid=(l+r)>>1;
if(x<=mid) update(l,mid,x,y,ls[p],ls[q]);
else update(mid+1,r,x,y,rs[p],rs[q]);
}
int query(int l,int r,int x,int p) {
if(l==r) return num[p];
int mid=(l+r)>>1;
if(x<=mid) return query(l,mid,x,ls[p]);
else return query(mid+1,r,x,rs[p]);
}
void insert(int l,int i) {
int p=lst,x=w[i];
if(w[i-len[p]-1]!=w[i]||i-len[p]-1<l) p=query(0,25,x,root[p]);
if(!ch[p][x]) {
int np=++cnt; len[np]=len[p]+2;
fail[np]=ch[query(0,25,x,root[p])][x];
update(0,25,w[i-len[fail[np]]],fail[np],root[np],root[fail[np]]);
ch[p][x]=np;
}
lst=ch[p][x];
if(len[lst]==i-l+1) lst2=lst;
}
void work(int l,int r,int x,int p) {
if(l==r) {
qui[x][l]=num[p]; return ;
}
int mid=(l+r)>>1;
work(l,mid,x,ls[p]); work(mid+1,r,x,rs[p]);
}
void extend(int r,int i) {
int p=lst,x=w[i];
if(w[i+len[p]+1]!=w[i]||i+len[p]+1>r) p=qui[p][x];
lst=ch[p][x];
}
int main() {
scanf("%d%d%d%s",&type,&n,&Q,w+1);
int i,j;
for(i=1;i<=n;i++) w[i]-='a';
fail[0]=fail[1]=1; len[1]=-1;
for(i=0;i<26;i++) update(0,25,i,1,root[0],root[0]),update(0,25,i,1,root[1],root[1]);
size=sqrt(n);
blo=(n+size-1)/size;
for(i=1;i<=n;i++) bl[i]=(i-1)/size+1;
for(i=1;i<=blo;i++) {
L[i]=R[i-1]+1; R[i]=min(i*size,n);
lst=lst2=0; tot++;
for(j=L[i];j<=n;j++) {
ans[i][j]=ans[i][j-1];
insert(L[i],j);
LST[i][j]=lst2;
if(vis[lst]!=tot) {
mn[i][lst]=j;
vis[lst]=tot; ans[i][j]++;
}
}
}
for(i=0;i<=cnt;i++) {
work(0,25,i,root[i]);
}
int l,r,Ans=0;
while(Q--) {
scanf("%d%d",&l,&r); l^=(Ans*type),r^=(Ans*type);
if(l>r) swap(l,r); lst=0; tot++;
if(bl[l]==bl[r]) {
Ans=0;
for(i=r;i>=l;i--) {
extend(r,i);
if(vis[lst]!=tot) {
vis[lst]=tot, Ans++;
}
}
}else {
int p=bl[l];
Ans=ans[p+1][r]; lst=LST[p+1][r];
for(i=R[p];i>=l;i--) {
extend(r,i);
if(vis[lst]!=tot) {
vis[lst]=tot;
if(!mn[p+1][lst]||mn[p+1][lst]>r) Ans++;
}
}
}
printf("%d\n",Ans);
}
}
BZOJ4044: [Cerc2014] Virus synthesis
https://lydsy.com/JudgeOnline/problem.php?id=4044
分析:
- 最后肯定是一个回文串加若干一操作。
- \(f[i]\)表示造出\(i\)的最少次数。
- 显然\(f[i]\)可以从\(fa\)和\(fail\)转移,需要注意从\(fa\)转移是\(+1\), 这些是一操作。
- 求出\(half[i]\)表示\(i\)的最长不超过一半的回文后缀,从这里填一些字符然后倍长。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 200050
int ch[N][4],fail[N],len[N],cnt,fa[N],n,w[N],f[N];
int g[20][N],lst,half[N];
char s[N];
void insert(int i) {
int p=lst,x=w[i];
for(;w[i-len[p]-1]!=w[i];p=fail[p]) ;
if(!ch[p][x]) {
int np=++cnt, q=fail[p];
for(;w[i-len[q]-1]!=w[i];q=fail[q]) ;
fail[np]=ch[q][x];
g[0][np]=ch[q][x];
ch[p][x]=np;
len[np]=len[p]+2;
fa[np]=p;
}
lst=ch[p][x];
}
int trans(char c) {
if(c=='A') return 0;
else if(c=='T') return 1;
else if(c=='C') return 2;
else return 3;
}
void init() {
cnt=1; fail[0]=fail[1]=1; len[1]=-1; g[0][0]=g[0][1]=1;
lst=0;
}
int jmp(int x) {
// int y=x;
// while(y!=1) {
// if(len[y]*2>len[x]) y=fail[y];
// else {
// f[x]=min(f[x],f[y]
// }
// }
// return y;
int i;
int t=x;
for(i=18;i>=0;i--) {
if(g[i][x]!=-1) {
if(len[g[i][x]]*2>len[t]) {
x=g[i][x];
}
}
}
return fail[x];
}
int main() {
int T;
scanf("%d",&T);
while(T--) {
init();
scanf("%s",s+1);
n=strlen(s+1);
int i,j;
for(i=1;i<=n;i++) w[i]=trans(s[i]);
w[0]=-1;
for(i=1;i<=n;i++) insert(i);
for(i=1;(1<<i)<=cnt+1;i++) {
for(j=0;j<=cnt;j++) {
g[i][j]=g[i-1][g[i-1][j]];
}
}
int ans=n;
for(i=0;i<=cnt;i++) f[i]=0x3f3f3f3f;
f[1]=0, f[0]=0;
// printf("%d\n",cnt);
for(i=2;i<=cnt;i++) {
if(len[i]&1) {
f[i]=min(f[fail[i]]+len[i]-len[fail[i]],len[i]);
ans=min(ans,f[i]+n-len[i]);
continue;
}
int t=half[fa[i]];
if(len[t]*2==len[fa[i]]) {
f[i]=min(f[i],f[t]+2);
}
if(fa[i]!=0&&fa[i]!=1) f[i]=min(f[i],f[fa[i]]+1);
f[i]=min(f[i],f[fa[i]]+2);
f[i]=min(f[i],f[fail[i]]+len[i]-len[fail[i]]);
int h=jmp(i); half[i]=h;
f[i]=min(f[i],f[h]+1+(len[i]-len[h]*2)/2);
ans=min(ans,f[i]+n-len[i]);
}
// for(i=2;i<=cnt;i++) printf("half[%d]=%d f=%d fa=%d len=%d\n",i,half[i],f[i],fa[i],len[i]);
printf("%d\n",ans);
for(i=1;(1<<i)<=cnt;i++) for(j=1;j<=cnt;j++) g[i][j]=-1;
for(i=0;i<=cnt;i++) fail[i]=0,memset(ch[i],0,sizeof(ch[i]));
}
}