题目链接:https://nanti.jisuanke.com/t/41389
题意:给定3e5的字符串,求每个不同的回文串中不同的字母总和
思路:预处出Pre保存前缀和以此来计算区间内不同字母的个数,构造回文树,那么每个回文串对答案的贡献就是串的长度乘上串出现的次数了
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
typedef long double ld;
const int maxn = 3e5 + 100;
const int mod = 1e9 + 7;
const int inf = 1e9;
char str[maxn];
ll fail[maxn],len[maxn],last,ch[maxn][26],n,num[maxn],cnt[maxn],tot,pre[maxn][26],pos[maxn];
//len[i], 以i结尾的最长回文子串的长度
//cnt[i]:以i结尾的最长回文子串相同的子串的个数 在count后得到全部
//num[i] 表示以i结尾的回文串的种类数
//str[] 存放添加的字符
//fail[] 失配后跳转到的不等于自身的最长后缀回文子串。
//pos[] 表示当前最长回文子串的结束位置
//ch[][] 类似于字典树,指向当前字符串在两端同时加上一个字符
//last 最新添加的回文节点
// tot 总的节点个数
// n表示添加的字符个数
ll newnode(ll x)
{
for(ll i = 0; i < 26; i++) //for 非多次调用回文树可省略
ch[tot][i] = 0;
cnt[tot] = 0;
num[tot] = 0;
len[tot] = x;
return tot++;
}
void init()
{
tot = 0;
last = 0;
newnode(0); //偶串根节点
newnode(-1); //奇串根节点
str[0] = '#'; //0号位置设置成一个不会出现的字符
fail[0] = 1; //两个根节点互指
fail[1] = 0;
return ;
}
ll get_fail(ll p,ll x)
{
while(str[ x-len[p]-1 ] != str[x] )
p = fail[p];
return p;
}
void count()
{
for(ll i = tot-1; i >= 0; i--)
cnt[ fail[i] ] += cnt[i];
//父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!
return ;
}
ll get_num(ll l,ll r)
{
ll res = 0;
for(ll i = 0; i < 26; i++)
{
if(pre[r][i] - pre[l-1][i] > 0)
res++;
}
return res;
}
void build()
{
for(ll i = 1; i <= n; i++) //构建回文树
{
ll tmp = get_fail(last,i);
ll s = str[i]-'a';
if(!ch[tmp][s])
{
ll now = newnode(len[tmp]+2);
fail[now] = ch[ get_fail( fail[tmp],i ) ][ s ];
ch[tmp][s] = now;
num[now] = num[fail[now]]+1;
}
last = ch[tmp][s];
pos[last] = i;
cnt[last]++;
}
return ;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin >> str+1;
n = strlen(str+1);
for(ll i = 1; i <= n; i++)
{
for(ll j = 0; j < 26; j++)
{
pre[i][j] = pre[i-1][j];
}
pre[i][ str[i]-'a' ]++;
}
init();
build();
count();
ll ans = 0;
for(ll i = 2; i <= tot-1; i++)
{
ll l = pos[i] - len[i] + 1, r = pos[i];
ans += get_num(l,r)*cnt[i];
}
cout << ans << endl;
return 0;
}