字符串系列(六)——回文自动机

想不到吧我还在更新哈哈哈哈哈。
这里写图片描述
今天带来一个2014年发明的算法——回文自动机。
既然写了这篇博客,那我就说得全面一些。

回文自动机

这个算法也称回文树。
可以类比一些其他的自动机,回文自动机也是有许多节点、许多next、许多fail的。

1、节点的那些事

每个节点都表示一个回文子串(注意了啊,只表示一个,SAM才是一类),且任意两个节点所表示的串必定不全等。(可以用数归证明,一个长度为n的串,其本质不同的回文子串数在O(n)级别)

2、数组定义

next[i][c] n e x t [ i ] [ c ] 表示在i节点所代表的串的两端各添加一个字符c,得到的那个新回文子串所在的节点。
fail[i] f a i l [ i ] 表示i节点的串的最长回文后缀。
cnt[i] c n t [ i ] 表示这个串共出现了几次。
len[i] l e n [ i ] 表示节点i这个串的长度。
num[i] n u m [ i ] 表示这个串有多少回文后缀。

int nxt[N][26]; //i节点对应的回文串在两边各加一个字符后变成的节点编号
int fail[N]; //fail[i]表示的串是i的最长后缀回文串
int cnt[N]; //表示这个串出现过几次 
int num[N]; //节点i这个串的后缀有多少是回文的
int len[N]; //节点i表示的回文串的长度
int S[N]; //S[i]是第i次添加的字符
int last; //以n结束的最长回文串所在的节点
int n; //目前添加的字符个数
int p; //下一个新建节点的标号
3、回文串长度奇偶处理

在初始时,0节点赋0的长度,1节点赋-1的长度。last指向0,0的fail指向1。同时Str[0] = -1。

具体原因不讲。

inline int newnode(int l) {
    //新建节点
    for(int i = 0; i < 26; ++i) nxt[p][i] = 0;
    cnt[p] = num[p] = 0;
    len[p] = l;
    return p++;
}
inline void init() {
    p = 0;
    newnode(0);
    newnode(-1);
    last = n = 0;
    S[n] = -1;
    fail[0] = 1;
}
4、判断合法

新加进来一个字符时,只有 Str[Nnowlen[x]1]=Str[Nnow] S t r [ N n o w − l e n [ x ] − 1 ] = S t r [ N n o w ] 我们才可以说是能构成一个新的回文串(构成回文节点唯一方法,就是在已有基础上找一个节点,在其两端各拓展一个字符,也就是我们所说的找到一个x,使式子成立)。
于是我们就可以去不停给last跑fail,直到满足条件。

inline int get_fail(int x) {
    while(S[n - len[x] - 1] != S[n]) x = fail[x];
    return x;
}
5、加一个字符进来
inline void add(int c) {
    S[++n] = c;
    int cur = get_fail(last); //通过上一个回文串找这个串的匹配位置
    if(!nxt[cur][c]) {
        //这个回文串从未出现过
        int now = newnode(len[cur] + 2);
        fail[now] = nxt[get_fail(fail[cur])][c]; //和AC自动机简直不要太类似
        nxt[cur][c] = now;
        num[now] = num[fail[now]] + 1;
    }
    last = nxt[cur][c];
    ++cnt[last];
}
5、把cnt搞对
inline void dp() {
    for(int i = p - 1; i >= 0; --i) cnt[fail[i]]+= cnt[i];
}

加完所有串之后,dp一下才对。
这个东西极妙,最好背一下板子。

贴一个 APIO2014 【 A P I O 2014 】 回文串的 code c o d e

#include <cstdio>
#define N 300010
inline long long mymax(long long x, long long y) {return x > y ? x : y;}
struct Palindromic_Tree {
    int nxt[N][26]; //i节点对应的回文串在两边各加一个字符后变成的节点编号
    int fail[N]; //fail[i]表示的串是i的最长后缀回文串
    int cnt[N]; //表示这个串出现过几次 
    int num[N]; //节点i这个串的后缀有多少是回文的
    int len[N]; //节点i表示的回文串的长度
    int S[N]; //S[i]是第i次添加的字符
    int last; //以n结束的最长回文串所在的节点
    int n; //目前添加的字符个数
    int p; //下一个新建节点的标号
    inline int newnode(int l) {
        //新建节点
        for(int i = 0; i < 26; ++i) nxt[p][i] = 0;
        cnt[p] = num[p] = 0;
        len[p] = l;
        return p++;
    }
    inline void init() {
        p = 0;
        newnode(0);
        newnode(-1);
        last = n = 0;
        S[n] = -1;
        fail[0] = 1;
    }
    inline int get_fail(int x) {
        while(S[n - len[x] - 1] != S[n]) x = fail[x];
        return x;
    }
    inline void add(int c) {
        S[++n] = c;
        int cur = get_fail(last); //通过上一个回文串找这个串的匹配位置
        if(!nxt[cur][c]) {
            //这个回文串从未出现过
            int now = newnode(len[cur] + 2);
            fail[now] = nxt[get_fail(fail[cur])][c]; //和AC自动机简直不要太类似
            nxt[cur][c] = now;
            num[now] = num[fail[now]] + 1;
        }
        last = nxt[cur][c];
        ++cnt[last];
    }
    inline void dp() {
        for(int i = p - 1; i >= 0; --i) cnt[fail[i]]+= cnt[i];
    }
    inline void work() {
        init();
        char c = getchar();
        while(c >= 'a' && c <= 'z') {
            add(c - 'a');
            c = getchar();
        }
        dp(); long long ans = 0;
        for(int i = p - 1; i >= 0; --i) ans = mymax(ans, 1ll * cnt[i] * len[i]);
        printf("%lld", ans);
    }
}pdtree;
int main() {
    pdtree.work();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值