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]));
    	}
    }
    
  • 相关阅读:
    利用相关的Aware接口
    java 值传递和引用传递。
    权限控制框架Spring Security 和Shiro 的总结
    优秀代码养成
    Servlet 基础知识
    leetcode 501. Find Mode in Binary Search Tree
    leetcode 530. Minimum Absolute Difference in BST
    leetcode 543. Diameter of Binary Tree
    leetcode 551. Student Attendance Record I
    leetcode 563. Binary Tree Tilt
  • 原文地址:https://www.cnblogs.com/suika/p/10005554.html
Copyright © 2011-2022 走看看