回文树
基础插入算法
- 增量构造法
- 假设已经构造出(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(logsum))
代码:
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
",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
",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
",i,half[i],f[i],fa[i],len[i]);
printf("%d
",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]));
}
}