zoukankan      html  css  js  c++  java
  • 回文树

    基础插入算法

    • 增量构造法
    • 假设已经构造出(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]));
    	}
    }
    
  • 相关阅读:
    Asp.Net 编码和解码
    远程连接端口号改变的数据库服务器
    SqlServer:PIVOT操作
    细说Sql Server中的视图(上)(更新)
    ubuntu安装nginx和设置网站https访问
    批量修改git仓库地址脚本
    解决macOS升级之后每次使用ssh都要输入密码的问题
    hexo博客简易搭建教程
    dcoker入门,使用docker部署NodeJs应用
    腾讯云+校园扶持计划是bug还是福利
  • 原文地址:https://www.cnblogs.com/suika/p/10005554.html
Copyright © 2011-2022 走看看