回文自动机 PAM

回文自动机 PAM

https://oi-wiki.org/string/pam/#_4

临时口胡

2014 Asia Xian Regional Contest G

给出两个字符串A,B。求A的所有回文串在B中出现次数的和。

可能是对两个树搜索公共部分···


2019牛客暑期多校训练营(第六场)C:Palindrome Mouse(回文树+树剖)

https://blog.csdn.net/cymbals/article/details/98398375

给定字符串Str,求出回文串集合为S,问S中的(a,b)满足a是b的子串的对数。

https://ac.nowcoder.com/acm/contest/886/C

我感觉,或许和链深度有关。


luogu 最长双回文串

输入长度为nnn的串SSS,求SSS的最长双回文子串TTT,即可将TTT分为两部分XXX,YYY,(∣X∣,∣Y∣≥1|X|,|Y|≥1∣X∣,∣Y∣≥1)且XXX和YYY都是回文串。

我觉得是排除掉直接fail连向根的节点去找。


拉拉队排练

大致意思就是让你求出一个字符串中所有的奇回文串,并把它们的长度前k的长度连乘

由于答案可能很大,输出除以19930726的余数。

马拉车也可做,但是回文树奇根和偶根是独立的,也可以操作的样子。

(但我有奇偶分离的马拉车,其实还好。


HDU 5421 Victor and String

BestCoder Round #52 (div.1)

给你n次操作,

如果为1,则在字符串后面插入一个字符,

如果为2,则在字符串前面插入一个字符,

如果为3,则输出当前的字符串中的本质不同的回文串的个数,

如果为4,则输出字符串的回文串的个数。

看起来要双向维护pam,这个变形比较需要基本原理,有点难度。

CodeChef Palindromeness

细枝末节的考虑

struct 还是 namespace 这是个问题。

两者各有各的好处。

namespace 的优势在于,如果你的逻辑都在一个模板里改,那么会很方便,但是在外面需要敲范围解析运算符::。而且在某些情况下,会避免初始化的问题。

struct 的优势在于,要根外部逻辑结合的时候,就一个点符号是比较方便的。而且容易让人去意识到作用域的问题。

我觉得两种方法都有可取的地方。

现场赛的时候甚至可能要直接敲成全局(如果是裸题的话)

2019 ICPC 徐州站 网络赛 G (unarchieved)

https://nanti.jisuanke.com/t/41389

题意

设一个回文串的贡献为其包含的不同字符的个数,则给出一个原串,求其所有回文子串的贡献之和。

思路

知道PAM可以遍历所有的回文子串,那么只要得知每个回文子串的位置即可。

这个信息很好维护。

至于求权值可以借助前缀做。

代码

namespace版本

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int PAMN = 3e5 + 59;
namespace PAM {
    int tot, las;
    int ch[PAMN][26];
    int cnt[PAMN];
    int len[PAMN];
    int fail[PAMN];
    int npos[PAMN];

    int pos;
    char s[PAMN];

    int node(int l) {
        tot++;
        memset(ch[tot], 0, sizeof(ch[tot]));
        len[tot] = l;
        cnt[tot] = 0;
        fail[tot] = 0;
        npos[tot] = pos;	// 标记回文串第次出现的位置
        return tot;
    }

    void init() {
        tot = -1;
        las = 0;
        pos = 0;
        s[pos] = '$';   // s = '$......'
        node(0);      // node[0] : len =  0 偶根
        node(-1);     // node[1] : len = -1 奇根
        fail[0] = 1;
    }

    int jump(int u) {
        while (s[pos - len[u] - 1] != s[pos]) {
            u = fail[u];
        }
        return u;
    }

    void extend(char c) {
        s[++pos] = c;
        int d = c - 'a';
        int u = jump(las);
        if (!ch[u][d]) {
            int v = node(len[u] + 2);
            fail[v] = ch[jump(fail[u])][d];
            ch[u][d] = v;
        }
        npos[ch[u][d]] = pos; // 尝试表示每个回文串最后的位置。(删掉也对)
        las = ch[u][d];
        cnt[las]++;
    }

    int arr[PAMN][26];

    ll solve() {
        ll ans = 0;

        for (int i = tot; i > 1; i--) {
            cnt[fail[i]] += cnt[i];
        }

        for (int i = 1; i <= pos; ++i) {
            memcpy(arr[i], arr[i - 1], sizeof arr[0]);
            arr[i][s[i] - 'a']++;
        }

        for (int i = 2, l, r; i <= tot; i++) {
            ll tmp = 0;
            r = npos[i];
            l = r - len[i];
            for (int j = 0; j < 26; ++j) {
                tmp += ll(arr[l][j] < arr[r][j]);
            }
            ans += tmp * cnt[i];
        }
        return ans;
    }
};  // namespace pam

char s[PAMN];

int main() {
    PAM::init();
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    for (int i = 1; i <= n; i++) {
        PAM::extend(s[i]);
    }
    printf("%lld\n", PAM::solve());
    return 0;
}

struct版本

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int PAMN = 3e5 + 59;

struct PAM {
    int tot, las;
    int ch[PAMN][26];
    int cnt[PAMN];
    int len[PAMN];
    int fail[PAMN];
    int npos[PAMN];

    int pos;
    char s[PAMN];

    int node(int l) {
        tot++;
        memset(ch[tot], 0, sizeof(ch[tot]));
        len[tot] = l;
        cnt[tot] = 0;
        fail[tot] = 0;
        npos[tot] = pos;
        return tot;
    }

    void init() {
        tot = -1;
        las = 0;
        pos = 0;
        s[pos] = '$';   // s = '$......'
        node(0);      // node[0] : len =  0 偶根
        node(-1);     // node[1] : len = -1 奇根
        fail[0] = 1;
    }

    int jump(int u) {
        while (s[pos - len[u] - 1] != s[pos]) {
            u = fail[u];
        }
        return u;
    }

    void extend(char c) {
        s[++pos] = c;
        int d = c - 'a';
        int u = jump(las);
        if (!ch[u][d]) {
            int v = node(len[u] + 2);
            fail[v] = ch[jump(fail[u])][d];
            ch[u][d] = v;
        }
        las = ch[u][d];
        cnt[las]++;
    }
} pam;

char s[PAMN];
int arr[PAMN][26];


int main() {
    pam.init();
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    for (int i = 1; i <= n; i++) {
        pam.extend(s[i]);
    }

    ll ans = 0;

    for (int i = pam.tot; i > 1; i--) {
        pam.cnt[pam.fail[i]] += pam.cnt[i];
    }

    for (int i = 1; i <= n; ++i) {
        memcpy(arr[i], arr[i - 1], sizeof arr[0]);
        arr[i][s[i] - 'a']++;
    }

    for (int i = 2, l, r; i <= pam.tot; i++) {
        ll tmp = 0;
        r = pam.npos[i];
        l = r - pam.len[i];
        for (int j = 0; j < 26; ++j) {
            tmp += ll(arr[l][j] < arr[r][j]);
        }
        ans += tmp * pam.cnt[i];
    }

    printf("%lld\n", ans);
    return 0;
}

2018 ICPC 南京站 网络赛 I Skr

https://nanti.jisuanke.com/t/A1955

题意

长度为n的数字串,只包含数字1~9,求所有本质不同的回文串代表的整数之和

思路

维护前缀数字的值,PAM枚举所有回文区间,乘上系数减一下就可以了。

代码

/**
 *  @Source: myself
 *  @Author: Tieway59
 *  @Complexity: $O(|S|)$
 *  @Description:
 *      长度为n的数字串,只包含数字1~9,求所有本质不同的回文串代表的整数之和
 *      MOD 1e9+7
 *
 *  @Example:
 *      "1232111":ans = 1+11+111+2+3+232+12321 = 12681
 *
 *  @Verification:
 *      2018 ICPC 南京站 网络赛 I Skr
 *      https://nanti.jisuanke.com/t/A1955
 */

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int PAMN = 2e6 + 59;

struct PAM {
    int tot, las;
    int ch[PAMN][26];
//    int cnt[PAMN];
    int len[PAMN];
    int fail[PAMN];
    int npos[PAMN];

    int pos;
    char s[PAMN];

    int node(int l) {
        tot++;
        memset(ch[tot], 0, sizeof(ch[tot]));
        len[tot] = l;
//        cnt[tot] = 0;
        fail[tot] = 0;
        npos[tot] = pos;
        return tot;
    }

    void init() {
        tot = -1;
        las = 0;
        pos = 0;
        s[pos] = '$';   // s = '$......'
        node(0);      // node[0] : len =  0 偶根
        node(-1);     // node[1] : len = -1 奇根
        fail[0] = 1;
    }

    int jump(int u) {
        while (s[pos - len[u] - 1] != s[pos]) {
            u = fail[u];
        }
        return u;
    }

    void extend(char c) {
        s[++pos] = c;
        int d = c - '0';
        int u = jump(las);
        if (!ch[u][d]) {
            int v = node(len[u] + 2);
            fail[v] = ch[jump(fail[u])][d];
            ch[u][d] = v;
        }
        las = ch[u][d];
//        cnt[las]++;
    }
} pam;

char s[PAMN];
ll pre[PAMN];
const ll MOD = 1e9 + 7;

ll fpow(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

int main() {
    pam.init();
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    for (int i = 1; i <= n; i++) {
        pam.extend(s[i]);
    }

    ll ans = 0;

    for (int i = 1; i <= n; ++i) {
        pre[i] = (pre[i - 1] * 10 + s[i] - '0') % MOD;
    }

    for (int i = 2; i <= pam.tot; ++i) {
        int len = pam.len[i];
        int r = pam.npos[i];
        int l = r - len;
        ans = (ans + pre[r] + MOD - pre[l] * fpow(10, len) % MOD) % MOD;
    }

    printf("%lld\n", ans);
    return 0;
}

2020 ICPC UNCPC H

题意

统计一个串长度大于1并且是奇数的本质不同回文子串个数。

思路

就枚举所有状态即可。

代码

/**
 *  @Source: myself
 *  @Author: Tieway59
 *  @Complexity: $O(N)$
 *  @Description:
 *      统计一个串长度大于1并且是奇数的本质不同回文子串个数。
 *
 *  @Example:
 *      5
 *      ababa
 *
 *      ans = 3
 *
 *  @Verification:
 *      2020 ICPC UNCPC H. Happy game
 *      https://codeforces.ml/gym/102700/problem/H
 */

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int PAMN = 1e5 + 59;

struct PAM {
    int tot, las;
    int ch[PAMN][26];
//    int cnt[PAMN];
    int len[PAMN];
    int fail[PAMN];
//    int npos[PAMN];

    int pos;
    char s[PAMN];

    int node(int l) {
        tot++;
        memset(ch[tot], 0, sizeof(ch[tot]));
        len[tot] = l;
//        cnt[tot] = 0;
        fail[tot] = 0;
//        npos[tot] = pos;
        return tot;
    }

    void init() {
        tot = -1;
        las = 0;
        pos = 0;
        s[pos] = '$';   // s = '$......'
        node(0);      // node[0] : len =  0 偶根
        node(-1);     // node[1] : len = -1 奇根
        fail[0] = 1;
    }

    int jump(int u) {
        while (s[pos - len[u] - 1] != s[pos]) {
            u = fail[u];
        }
        return u;
    }

    void extend(char c) {
        s[++pos] = c;
        int d = c - 'a';
        int u = jump(las);
        if (!ch[u][d]) {
            int v = node(len[u] + 2);
            fail[v] = ch[jump(fail[u])][d];
            ch[u][d] = v;
        }
        las = ch[u][d];
//        cnt[las]++;
    }
} pam;

char s[PAMN];
ll pre[PAMN];
const ll MOD = 1e9 + 7;

ll fpow(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

int main() {
    pam.init();
    int n;
    scanf("%d %s", &n, s + 1);
    for (int i = 1; i <= n; i++) {
        pam.extend(s[i]);
    }

    ll ans = 0;

    for (int i = 2; i <= pam.tot; ++i) {
        if (pam.len[i] > 1)
            ans += pam.len[i] % 2;
    }

    printf("%lld\n", ans);
    return 0;
}

2019 杭电多校2 1009

/**
 *  @Source:myself
 *  @Author: Tieway59
 *  @Complexity: $O(N)$
 *  @Description:
 *      给出一个长度为N的字符串,要求输出一个长度为N的统计数组A
 *      A[i]表示长度为i的good substring的数量
 *      good substring :该子串是回文串,且该子串的一半也是回文串。
 *      做法是子串哈希,PAM枚举所有子串,检查。
 *
 *  @Example:
 *      abababa
 *      7 0 0 0 3 0 0
 *
 *  @Verification:
 *      2019 杭电多校2 1009 I Love Palindrome String
 *      http://acm.hdu.edu.cn/showproblem.php?pid=6599
 */

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int PAMN = 3e5 + 59;

struct PAM {
    int tot, las;
    int ch[PAMN][26];
    int cnt[PAMN];
    int len[PAMN];
    int fail[PAMN];
    int npos[PAMN];

    int pos;
    char s[PAMN];

    int node(int l) {
        tot++;
        memset(ch[tot], 0, sizeof(ch[tot]));
        len[tot] = l;
        cnt[tot] = 0;
        fail[tot] = 0;
        npos[tot] = pos;    //记录第一次出现的位置
        return tot;
    }

    void init() {
        tot = -1;
        las = 0;
        pos = 0;
        s[pos] = '$';   // s = '¥......'
        node(0);      // node[0] : len =  0 偶根
        node(-1);     // node[1] : len = -1 奇根
        fail[0] = 1;
    }

    int jump(int u) {
        while (s[pos - len[u] - 1] != s[pos]) {
            u = fail[u];
        }
        return u;
    }

    void extend(char c) {
        s[++pos] = c;
        int d = c - 'a';
        int u = jump(las);
        if (!ch[u][d]) {
            int v = node(len[u] + 2);
            fail[v] = ch[jump(fail[u])][d];
            ch[u][d] = v;
        }
        las = ch[u][d];
        cnt[las]++;
//        npos[las] = pos;    // 记录最后出现的位置
    }
} pam;

char s[PAMN];
typedef long long ll;
const ll MOD = 1e9 + 7;
ll pre[PAMN];
ll ans[PAMN];

ll fpow(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

ll subhush(int l, int r) {
    l--;
    return (pre[r] + MOD - pre[l] * fpow(19491001, r - l) % MOD) % MOD;
}

int main() {

    while (scanf("%s", s + 1) != EOF) {
        int n = strlen(s + 1);
        pam.init();
        for (int i = 1; i <= n; i++) {
            pam.extend(s[i]);
            ans[i] = 0;
            pre[i] = 0;
        }

        for (int i = 1; i <= n; ++i) {
            pre[i] = (pre[i - 1] * 19491001 + (s[i] - 'a')) % MOD;
        }

        for (int i = pam.tot; i > 1; --i) {
            pam.cnt[pam.fail[i]] += pam.cnt[i];
        }

        for (int i = 2; i <= pam.tot; ++i) {
            int r = pam.npos[i];
            int l = r - pam.len[i] + 1;
            int len = pam.len[i];
            int m = (l + r) >> 1;
            if (len & 1) {
                if (subhush(l, m) == subhush(m, r)) {
                    ans[len] += pam.cnt[i];
                }
            } else {
                if (subhush(l, m) == subhush(m + 1, r)) {
                    ans[len] += pam.cnt[i];
                }
            }
        }

        for (int i = 1; i <= n; ++i) {
            printf("%lld%c", ans[i], " \n"[i == n]);
        }
    }

    return 0;
}

洛谷 P4555 [国家集训队]最长双回文串

/**
 *  @Source:myself
 *  @Author: Tieway59
 *  @Complexity: $O(N)$
 *  @Description:
 *      输入长度为n的串S,求S的最长双回文子串T,即可将T分为两部分X,Y,
 *      ∣X∣,∣Y∣≥1|X|,|Y|≥1∣X∣,∣Y∣≥1 且X和Y都是回文串。
 *
 *      转化成枚举后缀拼接就很好做了。
 *      思路是用PAM处理每个位置i的最长回文后缀,lb[i] (反向是rb[i])
 *      $ans = max(lb[i] + rb[i + 1])$
 *
 *  @Example:
 *      baacaabbacabb
 *      ans := 12
 *
 *  @Verification:
 *      洛谷 P4555 [国家集训队]最长双回文串
 *      https://www.luogu.com.cn/problem/P4555
 */

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int PAMN = 1.2e5 + 59;

struct PAM {
    int tot, las;
    int ch[PAMN][26];
    int cnt[PAMN];
    int len[PAMN];
    int fail[PAMN];
//    int npos[PAMN];

    int pos;
    char s[PAMN];

    int node(int l) {
        tot++;
        memset(ch[tot], 0, sizeof(ch[tot]));
        len[tot] = l;
        cnt[tot] = 0;
        fail[tot] = 0;
//        npos[tot] = pos;    //记录第一次出现的位置

        return tot;
    }

    void init() {
        tot = -1;
        las = 0;
        pos = 0;
        s[pos] = '$';   // s = '¥......'
        node(0);      // node[0] : len =  0 偶根
        node(-1);     // node[1] : len = -1 奇根
        fail[0] = 1;

    }

    int jump(int u) {
        while (s[pos - len[u] - 1] != s[pos]) {
            u = fail[u];
        }
        return u;
    }

    void extend(char c, int lb[]) {
        s[++pos] = c;
        int d = c - 'a';
        int u = jump(las);
        if (!ch[u][d]) {
            int v = node(len[u] + 2);
            fail[v] = ch[jump(fail[u])][d];
            ch[u][d] = v;
        }
        las = ch[u][d];
        cnt[las]++;

        lb[pos] = max(lb[pos], len[las]);
//        rb[pos - len[las] + 1] = max(rb[pos - len[las] + 1], len[las]);

//        npos[las] = pos;    // 记录最后出现的位置
    }
} pam;

char s[PAMN];
int lb[PAMN];
int rb[PAMN];


int main() {

    scanf("%s", s + 1);
    int n = strlen(s + 1);

    pam.init();
    for (int i = 1; i <= n; i++) {
        lb[i] = 0;
        pam.extend(s[i], lb);
    }
    reverse(s + 1, s + 1 + n);
    pam.init();
    for (int i = 1; i <= n; i++) {
        rb[i] = 0;
        pam.extend(s[i], rb);
    }
    reverse(rb + 1, rb + 1 + n);

    int ans = 0;
    for (int i = 1; i < n; ++i) {
        ans = max(ans, lb[i] + rb[i + 1]);
    }

    printf("%d\n", ans);

    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值