[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');
本题关键
- 考虑问题,正难则反;
- 回文自动机特性:以一个点结尾的回文串个数就是它所代表的点在 \(fail\) 树上的深度;
另外,这个题卡空间,建议使用邻接表构建自动机 坑死我了.