题意:
给出一个串,对于每个回文串算出有几种字母,然后所有的回文串求和。
思路:
利用回文树处理本质相同的回文串计数,对于一种本质的回文串,利用一个右端点和长度,可以找出这种本质具体长什么样子,利用对26个字母做前缀和,O26判断一种本质的回文串有几种字母。(也可以用马拉车做)
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 3e5 + 5;
const int N = 26;
ll pre[MAXN][26];
struct Palindromic_Tree {
ll next[MAXN][N];//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
ll fail[MAXN];//fail指针,失配后跳转到fail指针指向的节点
ll cnt[MAXN];//cnt[i]表示节点i表示的本质不同的串的个数,需跑count函数
ll num[MAXN];//表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数
ll len[MAXN];//len[i]表示节点i表示的回文串的长度
ll S[MAXN];//存放添加的字符
ll last;//指向上一个字符所在的节点,方便下一次add
ll n;//字符数组指针
ll p;//节点指针
ll pos[MAXN];//某种本质的回文串的一个右端点
ll newnode(ll l) {//新建节点
for (ll i = 0; i < N; ++i) next[p][i] = 0;
cnt[p] = 0;
num[p] = 0;
len[p] = l;
return p++;
}
void init() {
p = 0;
newnode(0);
newnode(-1);
last = 0;
n = 0;
S[n] = -1;
fail[0] = 1;
}
ll get_fail(ll x) {
while (S[n - len[x] - 1] != S[n]) x = fail[x];
return x;
}
void add(ll c) {
c -= 'a';
S[++n] = c;
ll cur = get_fail(last);
if (!next[cur][c]) {
ll now = newnode(len[cur] + 2);
fail[now] = next[get_fail(fail[cur])][c];
next[cur][c] = now;
num[now] = num[fail[now]] + 1;
}
last = next[cur][c];
cnt[last]++;
pos[last] = n;
}
void count() {
for (ll i = p - 1; i >= 0; --i) {
cnt[fail[i]] += cnt[i];
}
}
ll get_ans() {
ll ret = 0;
for (ll i = 2; i < p; i++) {
ll r = pos[i];
ll l = r - len[i] + 1;
// printf("cnt:%lld l:%lld r:%lld\n", cnt[i], l, r);
ll sum = 0;
for (ll j = 0; j < 26; j++) {
if (pre[r][j] - pre[l - 1][j]) {
sum++;
}
}
ret += sum * cnt[i];
}
return ret;
}
} pam;
char ss[MAXN];
int main() {
pam.init();
scanf("%s", ss);
ll len = strlen(ss);
for (ll i = 1; i <= len; i++) {
for (ll j = 0; j < 26; j++) {
if (j == ss[i - 1] - 'a') {
pre[i][j] = pre[i - 1][j] + 1;
} else {
pre[i][j] = pre[i - 1][j];
}
}
}
for (ll i = 0; i < len; i++) {
pam.add(ss[i]);
}
pam.count();
printf("%lld\n", pam.get_ans());
return 0;
}