回文树

基础插入算法

  • 增量构造法
  • 假设已经构造出(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]));
	}
}
原文地址:https://www.cnblogs.com/suika/p/10005554.html