回文子串计数[自创](回文树)

题目描述

鳟鱼是一个精通魔法的大法师,现在他有一串咒语,是一个仅用小写字母构成的字符串s(1<=|s|<=300000)。
这个字符串个每个不同的回文子串(起始位置不同算相同,如"aa"中只有一个"a"回文子串)会造成1点A类伤害,每个位置不同(即起始位置不同也算不同,如"aa"中有两个位置不同的"a"回文子串)的回文子串会造成1点B类伤害。
鳟鱼想要知道他施这个魔法会对敌人造成多少点A类伤害,多少点B类伤害。
鳟鱼现在把这个任务交给了你。

输入格式

一个字符串s。

输出格式

两个整数a、b,代表a点A类伤害,b点B类伤害。

样例

输入样例1

xibobix

输出样例1

7 10

数据范围与提示

1<=|s|<=300000。
字符串只包含小写字母

我们先考虑这道题的第一部分。先把回文树建出来,很容易可以想到不同的回文子串的个数即为回文树节点的个数-2(除去odd和even)。

我们再考虑第二部分。我们用前缀和的思想,因为我们的回文树是增量构造的,假设我们已经求出s的数量,则sc的数量的s的数量+以c为结尾的回文串的数量,也就是最长回文后缀的fail链的长度-2(除去odd和even)。这样我们就求出了两种情况的答案。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 400000;
char s[N];
int lens;
long long ans;
namespace Plalindromic_Tree{
    struct node{
        int go[26];
        int fail, len;
        long long fail_len;
    }pt[N];
    int lst = 0, tot = 0;
    int cnt[N];
    void build() {
        s[0] = -1;
        pt[++tot].len = -1;
        pt[0].fail = pt[1].fail = 1;
        pt[1].fail_len = 1;
        pt[0].fail_len = 2;
    }
    int add(int c, int n) {
        int p = lst;
        while (s[n - pt[p].len - 1] != s[n]) p = pt[p].fail;
        if (!pt[p].go[c]) {
            int v = ++tot, k = pt[p].fail;
            pt[v].len = pt[p].len + 2; 
            while (s[n - pt[k].len - 1] != s[n]) k = pt[k].fail; 
            pt[v].fail = pt[k].go[c];
            pt[v].fail_len = pt[pt[k].go[c]].fail_len + 1; 
            pt[p].go[c] = v;
        }
        cnt[pt[p].go[c]]++;
        return lst = pt[p].go[c];
    }
}using namespace Plalindromic_Tree;
int main() {
    scanf("%s", s + 1);
    lens = strlen(s + 1);
    build();
    for (int i = 1; i <= lens; i++) {
        ans += pt[add(s[i] - 'a', i)].fail_len - 2;
    }
    cout << tot - 1 << " " << ans;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zcr-blog/p/12298703.html