回文树笔记(转自quack_quack)

1.回文树的next[charset]指针:
b->aba
那么就这样表示:b.next[a]=aba
当然树里面肯定不能存字符串,于是就直接用下标标号代替了
2.回文树的fail指针:
跟ac自动机类似,fail指针指向当前节点的最大回文后缀
没有就指向根
3.回文树的根
有2个根,一个单根就是往下连回文串长度为奇数的节点,本身长度为-1
还有个双根就是往下连回文串长度为偶数的节点,本身长度为0
双根的fail指向单根
当然,也可以像manacher那样,aab->&a&a&b&,这样只用单根就是一棵树了。
可以树DP或者可持久化什么的。。。
4.回文树节点的域
len->表示当前节点回文串的长度,一般做回文串长度的题会用
cnt->表示当前节点被插入过多少次,一般做计数类的题会用,一般需要配合count函数
代码模板:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=100005;
const int N=26;
struct Palindromic_Tree{
    int next[MAXN][N],fail[MAXN],cnt[MAXN],len[MAXN],S[MAXN],last,n,p;
    int newnode(int l)
    {
        for(int i=0;i<N;++i)next[p][i]=0;
        cnt[p]=0;
        len[p]=l;
        return p++;
    }
    void init()
    {
        p=0;
        newnode(0);
        newnode(-1);
        last=0;
        n=0;
        S[n]=-1;
        fail[0]=1;
    }
    int getfail(int x)
    {
        while(S[n-len[x]-1]!=S[n])x=fail[x];
        return x;
    }
    void insert(int c)
    {
        c-='a';
        S[++n]=c;
        int cur=getfail(last);
        if(!next[cur][c])
        {
            int now=newnode(len[cur]+2);
            fail[now]=next[getfail(fail[cur])][c];
            next[cur][c]=now;
        }
        last=next[cur][c];
        ++cnt[last];
    }
    void count()
    {
        for(int i=p-1;i>=0;--i)cnt[fail[i]]+=cnt[i];
    }
}pt;
char s[MAXN];
int main()
{
    scanf("%s",s);
    int n=strlen(s);
    pt.init();
    for(int i=0;i<n;i++)
        pt.insert(s[i]);
    pt.count();
    for(int i=0;i<pt.p;++i)
        printf("%d\n",pt.cnt[i]);
}

下面说说用这个模板怎么做回文树的题:
1.最长回文子串

for(int i=0;i<pt.p;++i)
    ans=max(ans,pt.len[i]);

很简单吧,如果要输出的话还要保存插入进去的字符的下标。
但是一般用manacher写这个题会更简单。
2.求字符串中有多少个本质不同的回文子串
例如abad,本质不同的回文字串有:a,b,d,aba,共4个。

ans=pt.p-2;

很简单吧,其实就是看看回文树里面除了根以外有几个节点。
3.对于一个空字符串s,每次在s末尾加上一个字符,对于每次操作,求字符串中有多少个本质不同的回文子串
其实跟上面代码一样。。每插入一次就算一次ans。提出这个只是为了说明回文树是动态的。当然,如果这个s支持删除操作那么应该会用到可持久化回文树。删除就得回到之前的版本。
4.求字符串每个回文子串出现过多少次
例如abad,本质不同的回文字串有:a,b,d,aba,共4个。
a出现过2次。
b出现过1次。
d出现过1次。
aba出现过1次。
终于用到cnt数组了。
上面那个模板其实就是解决这个问题的。
就是非常简单的树DP

void count()
{
    for(int i=p-1;i>=0;--i)cnt[fail[i]]+=cnt[i];
}

因为i这个节点的fail[i]就是i的最长回文后缀
假设i出现过cnt[i]次,那么fail[i]除了它自己出现cnt[fail[i]]次,还在i中出现了cnt[i]次。并且还要倒着从叶子节点往根推。
最后要输出答案的话,一般还是要保存下标
根据下标和回文串长度来按题意顺序输出cnt
5.求字符串中回文串的个数
例如abad。a出现过2次。b出现过1次。d出现过1次。aba出现过1次。
因此回文串有5个。
可以发现的是,刚才的树DP已经推到根了,那么根的cnt值就是答案。因为两个根之间有fail关系所以输出哪个根的cnt都可以。
6.求两个字符串的公共回文串的个数(2014-2015 ACM-ICPC, Asia Xian Regional Contest)
建两棵回文树,分别insert两个字符串。
然后分别从2个根开始沿着next数组下去dfs。
如果treea.next[cura][i] 和treeb.next[curb][i] 都存在
那么说明两个字符串都有相同的回文串,于是

cura=treea.next[cura][i];
curb=treeb.next[curb][i];
ans+=treea.cnt[cura]*treeb.cnt[curb];

然后递归下一层dfs(cura,curb);。
如果treea.next[cura][i] 和treeb.next[curb][i] 有一个不存在就不往下dfs了。
7.BZOJ 2565 最长双回文串
给出一个字符串,求所有子串中能分成前后两个部分都是回文串最长的子串的长度。
问题实际上是求两个这样的数组left[],right[],分别表示以某位置结尾往左或往右最长的回文子串。
这个怎么搞?
就是给出一个空字符串s,每次往s的末尾加一个字符,然后查询s里面包含末尾字符的最长的回文子串的长度。修改一下insert函数:

int insert(int c)
{
    c-='a';
    S[++n]=c;
    int cur=getfail(last);
    if(!next[cur][c])
    {
        int now=newnode(len[cur]+2);
        fail[now]=next[getfail(fail[cur])][c];
        next[cur][c]=now;
    }
    last=next[cur][c];
    ++cnt[last];
    return len[last];
}

insert函数的返回值就是要求的。
如果从左往右加字符,得到的结果就是left[],如果从右往左加字符,得到的就是right[]。然后枚举中间点,答案是left[i]+right[i],就可以找最大值。
所以需要2棵回文树(当然一棵用完了初始化再用一次也可以的)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值