回文树

基础插入算法

  • 增量构造法
  • 假设已经构造出\(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]));
	}
}
posted @ 2018-11-23 09:16  fcwww  阅读(388)  评论(0)    收藏  举报