首页 > 其他分享 >回文树

回文树

时间:2022-11-14 17:56:33浏览次数:32  
标签:ch int len fail inline 回文

回文树(EER Tree,Palindromic Tree)亦称回文自动机,用于存储一个串中的所有回文子串。

由于回文串可能有奇有偶,于是分为两棵树进行构建,偶跟编号为0,长度为0,fail指向奇根,奇根编号为1,长度为-1,fail指向自身。

fail指向的是该节点的最长回文后缀,新加入字符时跳fail直到其表示的回文串两侧都能添加该字符。

struct PalindromicAutomation{
    struct tree{
        int ch[26],fail,len,sz/*回文串后缀本质不同的回文串个数*/,cnt/*该回文串出现次数*/,trans/*长度小于自身一半的最长回文后缀*/;
    }t[N];
    int tot=1,last/*上一次插入字符的编号*/;
    #define ch(p,x) (t[p].ch[x])
    #define f(p) (t[p].fail)
    #define l(p) (t[p].len)
    #define s(p) (t[p].sz)
    #define c(p) (t[p].cnt)
    #define t(p) (t[p].trans)
    inline PalindromeAutamation(){
        f(0)=f(1)=tot=1;/*偶跟0指向奇根1,奇根指向自身*/
        l(1)=-1;/*奇根长度-1,插入第一个字符时+2变成1*/
    }
    inline int getfail(int x,int n){/*依次跳x的fail直到回文后缀的两端都可以添加s[n]*/
        while(n-l(x)-1<0||s[n]!=s[n-l(x)-1])x=f(x);/*左端点不合法或当前回文串两端无法拼接该字符时跳fail*/
        return x;
    }
    inline int gettrans(int x,int n){
        while((l(x)+2<<1)>l(tot)||s[n]!=s[n-l(x)-1])x=f(x);/*注意比较的长度是扩展后会加2的长度*/
        return x;
    }
    inline void insert(int x,int c){
        int p=getfail(last,x);/*找到上一个位置的最长回文后缀*/
        if(!ch(p,c)){
            f(++tot)=ch(getfail(f(p),x),c);/*更新新建节点的fail指针*/
            l(tot)=l(p)+2;/*长度比父亲长度大2*/
            ch(p,c)=tot;/*更新儿子*/
            s(tot)=s(f(tot))+1;/*本质不同回文串个数,即状态数*/
            if(l(tot)<=2)t(tot)=f(tot);/*自身长度小于2则trans就是自己的fail*/
            else t(tot)=ch(gettrans(t(p),x),c);/*否则跳trans*/
        }
        last=ch(p,c);/*上一个点插入后的编号*/
        c(last)++;/*出现次数*/
    }
    inline void makecount(){/*统计每个回文串出现的次数*/
        for(int i=tot;i;i--)c(f(i))+=c(i);/*倒序累加后缀和,因为一个回文串的fail即为其回文后缀,一定至少出现了和自己一样的次数*/
    }
    inline void clear(){
        for(int i=0;i<=tot;i++)for(int j=0;j<4;j++)ch(i,j)=0;
        f(0)=f(1)=tot=1;
        l(1)=-1;
        last=0;
    }
}pa;

双倍回文串,一个字符串是双倍回文串当且仅当它可以分为两个长度相同且均为2的倍数的回文串,求一个字符串的最长双倍回文串的长度。维护trans指针,一个字符串满足要求当且仅当其trans长度是自身的一半且为2的倍数。

    inline int db(){
        int re=0;
        for(int i=1;i<=tot;i++)if((l(t(i))<<1)==l(i)&&(l(t(i))&1)==0)re=max(re,l(i));
        return re;
    }

用两种操作构造出给定字符串,在字符串开头或末尾添加一个任意字符,或在开头或末尾添加一个该串的逆串,求最小步数。最终答案肯定由一个回文串和一些剩余部分组成,设f[i]表示形成回文树上i点表示的回文的最小步数,若由边x->y,则可在形成x前加一个字符后再形成y,则f[u]=f[x]+1,同时维护每个点的trans表示小于当前点长度一半的最长回文后缀,从trans[i]暴力拼接到len[i]/2,再一步拼接到i.

    inline int solve(){
        int ans=n;
        queue<int>q;
        for(int i=2;i<=tot;i++)f[i]=l(i);
        for(int i=0;i<=3;i++)if(ch(0,i))q.push(ch(0,i));
        while(!q.empty()){
            int x=q.front();
            q.pop();
            f[x]=min(f[x],f[t(x)]+1+(l(x)>>1)-l(t(x)));
            ans=min(ans,n-l(x)+f[x]);
            for(int i=0;i<=3;i++){
                if(!ch(x,i))continue;
                int y=ch(x,i);
                f[y]=min(f[y],f[x]+1);
                q.push(y);
            }
        }
        return ans;
    }

求两个字符串的最长公共回文子串。为两个串构建回文树,中间不要清空,分别标记走过的节点,若有一个点都被标记过,那么用它代表的回文串来更新答案。

    inline void solve(){
        int ma=0,cnt=0;
        for(int i=2;i<=tot;i++){
            if(flag[i][0]&&flag[i][1]){
                if(ma<l(i))ma=l(i),cnt=1;
                else if(ma==l(i))cnt++;
            }
        }
        cout<<ma<<' '<<cnt<<'\n';
    }

求两个串的公共回文串对数。两个串插入一棵回文树,中间不要清空,标记走过的节点,乘法原理计算答案。

    inline void makecount(){/*统计每个回文串出现的次数*/
        for(int i=tot;i;i--)c(f(i))+=c(i);/*倒序累加后缀和,因为一个回文串的fail即为其回文后缀,一定至少出现了和自己一样的次数*/
        for(int i=tot;i;i--)flag[f(i)][0]+=flag[i][0],flag[f(i)][1]+=flag[i][1];
        int cnt=0;
        for(int i=2;i<=tot;i++)cnt+=flag[i][0]*flag[i][1];
        cout<<cnt;
    }

求一个字符串每个长度有多少个特殊回文串,特殊回文串为自己是回文串且它的前一半也是回文串。考虑fail指针的含义,指向该节点的最长回文后缀,那么对fail指针建树的话,路径上的点就都是子节点的前缀,对于一个点只要自己长度一半也存在,那么这个点代表的回文串就是合法的。

struct PalindromicAutomation{
    struct tree{
        int ch[26],fail,len,sz,cnt;
        inline tree(int fail=0,int len=0,int sz=0,int cnt=0):fail(fail),len(len),sz(sz),cnt(cnt){
            memset(ch,0,sizeof(ch));
        }
    };
    int last;
    vector<tree>t;
    string s;
    #define ch(p,x) (t[p].ch[x])
    #define f(p) (t[p].fail)
    #define l(p) (t[p].len)
    #define s(p) (t[p].sz)
    #define c(p) (t[p].cnt)
    inline int getfail(int x){
        int n=s.size()-1;
        while(s[n-l(x)-1]!=s[n])x=f(x);
        return x;
    }
    inline PalindromicAutomation(){
        t.push_back(tree(1,0));
        t.push_back(tree(1,-1));
        s="~";
    }
    inline void clear(){
        last=0;
        t.clear();
        t.push_back(tree(1,0));
        t.push_back(tree(1,-1));
        s='~';
    }
    inline void insert(char c){
        s+=c;
        c-='a';
        int p=getfail(last);
        if(!ch(p,c)){
            t.push_back(tree(ch(getfail(f(p)),c),l(p)+2,s(p)+1));
            ch(p,c)=t.size()-1;
        }
        last=ch(p,c);
        c(last)++;
    }
    inline void makecount(){
        for(int i=t.size()-1;i;i--)c(f(i))+=c(i);
    }
    void dfs(int x,vector<int>&ans,vector<vector<int>>&e,vector<int>&len,vector<int>&ok){
        if(l(x)!=-1)len[l(x)]=1;
        if(len[l(x)+1>>1])ok[x]=1;
        for(int y:e[x])dfs(y,ans,e,len,ok);
        if(l(x)!=-1)len[l(x)]=0;
    }
    inline void getans(int n){
        vector<int>ans(n+1,0),len(n+1,0),ok(t.size(),0);
        vector<vector<int>>e(t.size());
        for(int i=0;i<t.size();i++)e[f(i)].push_back(i);
        dfs(0,ans,e,len,ok);
        makecount();
        for(int i=t.size()-1;~i;i--)if(ok[i])ans[l(i)]+=c(i);
        for(int i=1;i<=n;i++)cout<<ans[i]<<' ';
        cout<<'\n';
    }
}pa;

标签:ch,int,len,fail,inline,回文
From: https://www.cnblogs.com/safeng/p/16889805.html

相关文章

  • leetcode 5. 最长回文子串
    给你一个字符串 s,找到 s 中最长的回文子串。 示例1:输入:s="babad"输出:"bab"解释:"aba"同样是符合题意的答案。示例2:输入:s="cbbd"输出:"bb" 提示:1<......
  • leetcode 647. 回文子串 js实现
    给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。回文字符串 是正着读和倒过来读一样的字符串。子字符串 是字符串中的由连续字符组成的一个序列。......
  • 洛谷刷题_P217 [USACO1.5]回文质数 Prime Palindromes
    题目P217[USACO1.5]回文质数PrimePalindromes题目链接https://www.luogu.com.cn/problem/P1217知识点埃氏筛原理:要得到自然数n以内的全部素数,必须把不大于根号n......
  • 判断是否为回文字符串, StringBuffer对象的reverse()方法,返回值toString()
      import java.util.*;public class Solution {    /**     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可    ......
  • 力扣-647-回文子串
    因为单字符也算是回文,所以至少有n个然后感觉又是二维dp感觉很像回溯解决排列组合问题感觉难点在于还要判断是不是回文,虽然可以借助栈,但是每次都压栈弹栈肯定复杂度太大......
  • HDU 3608 最长回文
    ProblemDescription给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度.回文就是正反读都是一样的字符串,如aba,abba等 Inp......
  • 回文自动机PAM从菜到菜
    回文自动机基础操作两个初始状态一个长度为\(0,-1\)的偶回文根和奇回文根。转移\(\delta(x,c)\)从\(x\)节点代表的回文串转移到两端加入字符\(c\)后到达的节点......
  • P4555 最长双回文串 解题报告
    看到回文串,于是就想到了马拉车。马拉车可以帮我们求出每个\(i\)的最大扩展距离,容易得出,双回文串就是两个回文串拼一起。当然,两个回文串必须要相交,不然形不成一个字符串......
  • LeetCode125. 验证回文串
    验证回文串Day:2022-11-7link:https://leetcode.cn/problems/valid-palindromequestion:如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正......
  • 【模板】最长回文串长度 manacher
    \(pa_i\)表示以\(i\)为中心的(原串的)回文串长度#include<cstdio>#include<cstring>#include<algorithm>usingnamespacestd;typedeflonglongLL;intn,m,pa[......