**
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;
}