Palindrome Mouse

本文探讨了在字符串中寻找所有本质不同回文子串的方法,并利用回文树的特性进行优化,通过记忆化DFS搜索实现O(n)的时间复杂度。文章详细介绍了回文树的构造、维护以及如何统计子串之间的包含关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

**

Palindrome Mouse

**
题意:给出一串str,将串中本质不同的回文子串组成集合S,任取S中的两个元素(a,b),问a是b的子串的对数。
思路orz,比赛的时候画出回文树想了半小时没看出什么联系,后来补题时候看牛逼网友们的题解都是O(n)记忆化dfs搜索。
联想回文树的定义,回文树上一个节点其next边维护的父亲节点(除了0,1)都是儿子的子串, fail边维护的父亲节点也都是儿子的后缀子串,简单来说next边等价于往后加字符构成回文串,fail节点往前加构成回文串. 一个节点u往下能够延伸的数量记为down[u], 往上能够延伸的数量记为up[u], 所以一个节点u的贡献就等于 上下相乘down[u] * up[u] - 1; 减去1是减去它本身。

note:往next走和往fail走可能会有重复的地方,打个标记。统计完再取消标记。
AC code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;

const int mx = 26;
struct PAM{
    int ch[maxn][mx], f[maxn], len[maxn], s[maxn];
    int cnt[maxn];
    int num[maxn];
    int last, sz, n;
    int newnode(int x){
        memset(ch[sz], 0, sizeof(ch[sz]));
        cnt[sz] = num[sz] = 0, len[sz] = x;
        return sz++;
    }
    void init(){
        sz = 0;
        newnode(0), newnode(-1);
        last = n = 0, s[0] = -1, f[0] = 1;
    }
    int get_fail(int u){
        while(s[n - len[u] - 1] != s[n]) u = f[u];
        return u;
    }
    void add(int c){
        c -= 'a';
        s[++n] = c;
        int u = get_fail(last);
        if(!ch[u][c]){
            int np = newnode(len[u] + 2);
            f[np] = ch[get_fail(f[u])][c];
            num[np] = num[f[np]] + 1;
            ch[u][c] = np;
        }
        last = ch[u][c];
        ++cnt[last];
    }
    void count(){
        for(int i = sz - 1; ~i; ++i) cnt[f[i]] += cnt[i];
    }
    bool vis[maxn];
    int down[maxn], up[maxn];
    int tmp[maxn];
    int dfs(int u){
        down[u] = 1, up[u] = 0;
        vector<int>tmp;
        for(int x = u; x > 1 && vis[x] == 0; x = f[x]){
            vis[x] = 1, ++up[u];
            tmp.push_back(x);
        }
        for(int i = 0; i < mx; ++i){
            if(ch[u][i] == 0) continue ;
            down[u] += dfs(ch[u][i]);
        }
        for(int i = 0; i < (int)tmp.size(); ++i){
            vis[tmp[i]] = 0;
        }
        return down[u];
    }
    ll solve(){
        dfs(0), dfs(1);
        ll ans = 0;
        for(int i = 2; i < sz; ++i){
            ans += (1LL * down[i] * up[i] - 1);
        }
        return ans;
    }
}pam;

char s[maxn];
int main(){
    int T, kase = 0;
    scanf("%d", &T);
    while(T--){
        scanf("%s", s);
        pam.init();
        for(int i = 0; s[i]; ++i){
            pam.add(s[i]);
        }
        printf("Case #%d: %lld\n", ++kase, pam.solve());
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值