回文树

个人博客
这个东西学会了AC自动机 理解这个应该不难,AC自动fail指针保存了一个最长匹配后缀,这个也差不多。这个保存了最长匹配后缀回文串。
举个例子
老子找了半天没找到原本看过的博客,只找到了这张图片。随便写几句混一混就过去了。
[外链图片转存失败(img-nQdd5HZI-1565181289944)(https://i.loli.net/2019/08/07/CVL9sbFBohiyI6p.png)]
回文树里面的next字典树差不多的意义,只不过字典树里面的一个节点是表示在一个节点后面加上一个字符,但next是前后各加一个字符,就是t变成tct变成ctc的区别,前后各加一个字符肯定是回文串。那么问题来了奇数的回文串怎么办,所以出现啊了两个根节点,一个0,一个-1,连在-1的表示是个奇数个数的回文串,看代码里面的
x-len[p]+1刚好也是自己,所以自己和自己一定是回文串没啥毛病。再给个图

fail差不多就是上图的意思.
解释下例子怎么构造的
开始的时候建立两个根节点,两个根节点一个长度为0,一个-1,将fail[0]=1,0表示长度为0,1号节点长度为-1,一开始加入a肯定是没有任何后缀,直接连上-1根节点,fail肯定是连上0,此时当前节点的回文后缀有a,空串和-1,然后添加b,a节点前面很显然没有b,所以无法匹配,然后匹配空串,空串前面是a所以还是没法匹配,又匹配-1,很显然这个一定是可以的,因为和自己肯定是相同的.也就是构建了这一部分。

然后下一个字符串还是b,此时后缀有b,空串和-1,匹配后缀b前一个是a很显然不匹配,匹配空串,前一个刚好是b所以此时,将bb连到0节点下面,fail指向父亲节点失陪的第一个能够匹配b的位置,这个有点难理解。

在代码中是这一行。
下一个字符是a,此时字符串最长回文后缀有bb,b,空串和-1,显然bb就直接匹配了,然后再更新fail,差不多就是这么个过程。
多余的不解释看别人博客去吧。
想深入了解,去看看 国家集训队2017年的论文

namespace Palindromic_Tree {
const int MAXN = 1000010;
const int N = 26;
int next[MAXN][N];//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
int fail[MAXN];//fail指针,失配后跳转到fail指针指向的节点
int cnt[MAXN];
int num[MAXN];
int right[MAXN]; // 以 i 结尾的字符串个数 下标  从 1 开始
int len[MAXN];//len[i]表示节点i表示的回文串的长度
int S[MAXN];//存放添加的字符
int last;//指向上一个字符所在的节点,方便下一次add
int n;//字符数组指针
int p;//节点指针

int newnode(int l) {//新建节点
    for (int i = 0; i < N; ++i)
        next[p][i] = 0;
    cnt[p] = 0;
    num[p] = 0;
    right[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 get_fail(int x) {//和KMP一样,失配后找一个尽量最长的
    while (S[n - len[x] - 1] != S[n])
        x = fail[x];
    return x;
}

int add(int c) {
    c -= 'a';
    S[++n] = c;
    int ct = 0;
    int cur = get_fail(last);//通过上一个回文串找这个回文串的匹配位置
    if (!next[cur][c]) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
        int now = newnode(len[cur] + 2);//新建节点
        fail[now] = next[get_fail(fail[cur])][c];//和AC自动机一样建立fail指针,以便失配后跳转
        next[cur][c] = now;
        num[now] = num[fail[now]] + 1;
        ct = num[now];
    }
    last = next[cur][c];
    cnt[last]++;
    return num[last];
}

void count() {
    for (int i = p - 1; i >= 0; --i)
        cnt[fail[i]] += cnt[i];
    //父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值