[CF17E]Palisection

题目

传送门

给定一个长度为 \(n\) 的小写字母串.问你有多少对相交的回文子 串(包含也算相交)。.

输入格式

第一行是字符串长度 \(n(1\le n\le 2*10^6)\),第二行字符串.

输出格式

相交的回文子串个数 \(\bmod 51123987\).

题解

首先,如果我们正向求解有多少个回文串相交有些困难,因为相交的长度不固定,甚至还有可能是包含关系,考虑正男(♂)难则反——反向求解有多少回文串没有交,这样我们只需要枚举分界点,计算在左边和在右边的回文串数量即可.

那么,这个题的问题就是在对于位置 \(i\),我们如何求出在它 左/右 边的回文串数量,这里主要介绍使用 \(\tt PAM\) 的解决方案.

我们考虑在加入一个字符 \(\tt s[i]\) 之后,结尾刚好在 \(i\) 的回文串的数量,这里给出性质:

以一个点结尾的回文串个数就是它所代表的点在 \(fail\) 树上的深度.

证明:\(\mathcal Obviously\).

对于在其右边的,我们倒着做一遍就可以了.

const int maxn = 2e6;
const int jzm = 51123987;

char s[maxn + 5]; int n;

int now;

struct PAM{
    // int son[maxn + 5][26];
    struct edge{int to, nxt, w;
        edge(const int T = 0, const int N = 0, const int W = 0) : to(T), nxt(N), w(W){}
    }e[maxn + 5];
    int tail[maxn + 5], ecnt;
    inline void add_edge(const int u, const int v, const int x){
        e[++ ecnt] = edge(v, tail[u], x); tail[u] = ecnt;
    }
    /** @brief get the son which means @p x to @p u */
    inline int get(const int u, const int x){
        for(int i = tail[u]; i; i = e[i].nxt) if(e[i].w == x)
            return e[i].to;
        return 0;
    }
    int fail[maxn + 5];
    int len[maxn + 5];
    int dep[maxn + 5];// the depth on the fail-tree
    int lst, cnt;
    inline int getfail(int x){
        while(s[now - len[x] - 1] != s[now]) x = fail[x];
        return x;
    }
    inline void add(const int x){
        int p = getfail(lst);
        // int u = son[p][x];
        int u = get(p, x);
        if(!u){
            u = ++ cnt, len[u] = len[p] + 2;
            // fail[u] = son[getfail(fail[p])][x];
            fail[u] = get(getfail(fail[p]), x);
            dep[u] = dep[fail[u]] + 1;
            // son[p][x] = u;
            add_edge(p, u, x);
        }
        lst = u;
    }
    PAM(){
        ecnt = 0, lst = cnt = 1;
        len[1] = -1, fail[0] = fail[1] = 1;
    }
    inline void clear(){
        rep(i, 0, cnt) tail[i] = fail[i] = len[i] = dep[i] = 0;
        ecnt = 0, lst = cnt = 1;
        len[1] = -1, fail[0] = fail[1] = 1;
    }
}pam;

inline void init(){
    n = readin(1);
    scanf("%s", s + 1);
    s[0] = 'z' + 1;// pay attention!
}

int pre[maxn + 5], suf[maxn + 5];

init();
rep(i, 1, n){
    pam.add(s[++ now]);
    pre[i] = pam.dep[pam.lst] % jzm;
    tot += pre[i];
    if(tot >= jzm) tot -= jzm;
}
reverse(s + 1, s + n + 1); now = 0;
pam.clear();
rep(i, 1, n){
    pam.add(s[++ now]);
    suf[i] = pam.dep[pam.lst] % jzm;
    suf[i] += suf[i - 1];
    if(suf[i] >= jzm) suf[i] -= jzm;
}
reverse(suf + 1, suf + n + 1);
ll ans = (1ll * tot * (tot - 1) / 2) % jzm;
rep(i, 1, n){
    ans = ans - 1ll * pre[i] * suf[i + 1] % jzm;
    if(ans < 0) ans += jzm;
}
writc(ans, '\n');

本题关键

  1. 考虑问题,正难则反;
  2. 回文自动机特性:以一个点结尾的回文串个数就是它所代表的点在 \(fail\) 树上的深度;

另外,这个题卡空间,建议使用邻接表构建自动机 坑死我了.

posted @ 2021-01-12 20:53  Arextre  阅读(78)  评论(0编辑  收藏  举报