2019牛客多校赛 第六场 C Palindrome Mouse (回文树)

题意:给你一个字符串,然后把所以本质不同的回文串放入集合,然后问你有多少pair(a,b),满足a,b是回文串,且a是b的子串,问这样的pair有多少对。

在一个回文树里面,1.每一个节点对应的回文串都next是树里其后代的子串,所以一个节点i和其后代组成的pair的个数现在是sz[i]-1。2.其次每一个节点i对应的回文串又都是fail指针指向他的节点j(fail[j]=i)的子串,也包括j的后代,可以理解为fail[i]是i子树里全部节点的子串,所以pair个数是sz[i]。所以一个节点的贡献的pair是t[i]*sz[i]-1,首先这里需要分情况看,可能fail[i]和sz[i]在dfs到i节点之前就已经算过了,t[i]就为1,没算过就为2。

t[i]为1的情况如下图所示

另外之所以只算 i节点的fai和i子树里的节点组成的pair数,是因为i的fail的fail和i子树里的节点组成的pair数已经算过了,因为回文树有个性质:i节点是其fail的fail的后代。

#include<bits/stdc++.h>
using namespace std;
#define ls rt<<1
#define rs (rt<<1)+1
#define ll long long
#define fuck(x) cout<<#x<<"     "<<x<<endl;
const int maxn=1e5+10;
const ll mod=1e9+7;
int d[4][2]={1,0,-1,0,0,1,0,-1};
char s[maxn];
const int MAXN = 1e5+10;//长度
const int N = 26;//字符集大小
struct Palindromic_Tree {
    int next[MAXN][N];//next指针,next指针和字典树类似,指向的回文子串为i节点对应的回文子串两端加上同一个字符ch构成
    int fail[MAXN];//fail指针,失配后跳转到fail指针指向的节点,fail指针指向的是i节点对应的回文子串的最长后缀回文子串(是真后缀),这个匹配过程与kmp有点类似,fail[i]表示节点i失配以后跳转到长度小于该串且以该节点表示回文串的最后一个字符结尾的最长回文串表示的节点
    int cnt[MAXN];//在调用count函数之后,cnt[i]表示i节点对应的回文子串的出现次数的准确值
    int num[MAXN];//在调用add函数之后返回num[last]可以得到以i位置的字符为尾的回文串个数
    int len[MAXN];//len[i]表示节点i表示的回文串的长度
    int S[MAXN];//存放添加的字符,
    int vis[maxn],sz[maxn],t[maxn];
    int last;//指向上一个字符所在的节点,方便下一次add
    int n;//字符数组指针,从1开始,到n结束
    int p;//节点指针,0指向偶根,1指向奇根,有效编号到p-1

    int newnode(int l) {//新建节点
        for (int i = 0; i < N; ++i) next[p][i] = 0;
        cnt[p] = 0;
        num[p] = 0;
        len[p] = l;
        fail[p]=0;
        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 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;
        }
        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的子回文串!
    }
    void dfs(int now){
        t[now]=1+(vis[fail[now]]==0);
        vis[now]++,vis[fail[now]]++,sz[now]=1;
        for(int i=0;i<=25;i++)
        {
            if(!next[now][i]) continue;
            dfs(next[now][i]);
            sz[now]+=sz[next[now][i]];
        }
        vis[now]--,vis[fail[now]]--;
    }
    ll proc(){
        ll ans=0;
        dfs(0);
        dfs(1);
        for(int i=2;i<=p-1;i++) ans+=sz[i]*t[i]-1;
        return ans;
    }
}pt;

int main(){
    int t,len,cas=0;
    scanf("%d",&t);
    while(t--) {
        scanf("%s",s+1);
        len = strlen(s + 1);
        pt.init();
        for (int i = 1; i <= len; i++)
            pt.add(s[i]);
        printf("Case #%d: %lld\n", ++cas,pt.proc());
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40642465/article/details/98968635