回文树(Palindromic Tree)+黑科技 学习笔记

回文树(Palindromic Tree)

最基本的回文树在网上用很多资料,在这里做简单的介绍。本文的重点是后面回文树的一些更广泛的应用。

注:本文的图片转自http://adilet.org/blog/25-09-14/,所以图中某些变量的定义可能与本文不同,需要注意。

定义

节点

回文树中的每个节点都对应了一个回文串。

这里写图片描述

特别注意在回文树中还有两个特殊的节点分别对应了空串和长度为 1 的串,是为了添加只有一个字符和两个字符的回文串时更方便,后面会提到。

有个结论是:一个字符串 s 本质不同的回文串个数是O(n)的,所以回文树中的节点数也是 O(n) 的。

证明:利用数学归纳发证明这个结论。
1.对于 |s|=1 的字符串,回文串的数量为1,结论成立。
2.对于 |s|>1 的字符串,假设 s 是在s加上一个字符 c 后组成的字符串,并且结论对s成立。现在只需要证明结论同时对 s 成立。考虑以新字符c为结尾组成的回文串。显然只有长度最长的回文串 t 可能是新的,因为其他的回文串都是t的后缀,由于 t 是回文串,肯定与t的某个前缀相同,换句话说就是之前肯定出现过。所以每增加一个字符最多增加一个回文串。
毕证。

在回文树中有两种边,一种是字符边,一种是对应后缀的 fail 边。

字符边

在回文树中假如一个节点 a 对应的字符串可以通过在前面和后面分别添加一个字符c而得到另一个节点 b 对应的字符串,那么就让a b 连一条c的字符边。

这里写图片描述

fail

在回文树中假如一个节点 a 对应的字符串的最长回文后缀是节点b所对应的字符串,那么就由 a b连一条 fail 边。

这里写图片描述

构造

构造由于比较简单就简略的讲一下…

考虑每次在字符串 s 的末尾添加一个新的字符x。新增的回文串可以被表示为原来满足前一个字符为 x 的一个回文后缀,在两端都增加一个x,根据上面的证明,我们知道只有最长的符合要求的回文后缀是有用的。

这里写图片描述

设原来最长的后缀回文串对应的节点是 a ,那么相当于要不断沿a fail 边走直到找到第一个满足要求的回文后缀 b ,假如b已经存在字符 x 的转移边,那证明这个回文串已经在前面出现过了。否则就新增一个代表新的回文串的节点c并由 b c连一条 x 的字符边。

这里写图片描述

假如我们新添加了一个节点,那么就要考虑它的fail边了。其实这个跟上面的过程差不多我们只需要继续沿着 b fail边,找到第二个符合要求的回文串 d ,设e d 的字符边x连向的节点(可能为空串),那么再连一条 c e的后缀边即可。

这里写图片描述

例题【APIO2014】回文串

题目大意:考虑一个只包含小写拉丁字母的符串 s 。我们定义s的一个子串 t 的“出现值”为t s 中的出现次数乘以t的长度。 请你求出 s 的所有 回文子串中的最大出现值。

裸题,直接在加入节点是顺便统计一下个数,最后按fail边自下而上更新一下即可。

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 3e5 + 5;
typedef long long LL;

struct Tree {int Len, Fail, Go[26];} Tr[MAXN];

char S[MAXN];
int len, Last, tot, i, Cnt[MAXN];

void InitTree() {
    Tr[0].Len = 0, Tr[1].Len = -1;
    tot = 1, Last = 0;
    Tr[0].Fail = 1;
}

int Get(int Now) {
    while (S[i - Tr[Now].Len - 1] != S[i]) Now = Tr[Now].Fail;
    return Now;
}

void Add(int c) {
    int t = Get(Last);
    if (!Tr[t].Go[c]) {
        Tr[++ tot].Len = Tr[t].Len + 2;
        Tr[tot].Fail = Tr[Get(Tr[t].Fail)].Go[c];
        Tr[t].Go[c] = tot;
    }
    Last = Tr[t].Go[c];
    Cnt[Last] ++;
}

LL GetAns() {
    LL Ans = 0;
    for (int i = tot; i > 1; i --) {
        Cnt[Tr[i].Fail] += Cnt[i]; 
        Ans = max(Ans, LL(Tr[i].Len) * LL(Cnt[i]));
    }
    return Ans;
}

int main() {
    freopen("palindrome.in", "r", stdin), freopen("palindrome.out", "w", stdout);

    scanf("%s", S + 1);
    len = strlen(S + 1);
    InitTree();
    for (i = 1; i <= len; i ++) Add(S[i] - 'a');
    printf("%lld", GetAns());
}

黑科技

预备队大佬不让我写…只能在预备队互测完才能放出来…

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值