题意:
给定一个字符串,要求找出所有回文子串中不同字母的个数的总和。
题目链接:https://nanti.jisuanke.com/t/41389
分析:
可以先利用 (Manacher) 求出以各个位置为中心的回文串的长度。关键在于如何求出一个回文串中不同字母的个数,根据回文串的性质,只需要求出一边即可。可以利用主席树,也可以先预处理出,每个位置前的各个字母最后出现的位置。计算时,只要判断该字母是否在回文串范围内,然后根据该位置离回文串的边界的距离来确定其贡献,累加求和。
代码:
//回文串
// 算法第一步就是:预处理字符串,做法是在每一个字符的左右都加上一个特殊字符(前提是这个字符在字符串没有出现过)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5+5;
char s[N]; //原始字符串
char sn[N << 1]; //扩充后的新字符串
int sen[N << 1];//表示以sn[i]为中心的最长回文的半径
int pos[N][30];
int init()//对原有的字符串进行扩充
{
int len = strlen(s),cnt=1;//s从0开始存
sn[0] = '$';
sn[1] = '#';
for (int i = 0; i < len;i++)
{
sn[++cnt] = s[i];
sn[++cnt] = '#';
}
sn[++cnt] = ' ';
return cnt;//返回扩充后的字符串长度
}
ll manacher()
{
int len = init();//预处理扩充
int id, mx = 0;
for (int i = 1; i < len;i++)
{
sen[i] = mx > i ? min(sen[2 * id - i], mx - i) : 1;
while(sn[i-sen[i]]==sn[i+sen[i]])
sen[i]++;
if(mx<i+sen[i])
{
id = i;
mx = i + sen[i];
}
}
ll res=0;
for(int i=2;i<len-1;i++)
{
int p=i/2-1;//cout<<"i="<<i<<endl;
int t=0;
if((sen[i]-1)&1) t=sen[i]/2;
else t=(sen[i]-1)/2;
for(int j=0;j<26;j++)
{
int l=p-t;
if(pos[p][j]>l) res+=(pos[p][j]-l);
}
}
return res;
}
int main()
{
scanf("%s", s);
int len=strlen(s);
for(int i=0;i<26;i++)
pos[0][i]=-1;
pos[0][s[0]-'a']=0;
for(int i=1;i<len;i++)
{
for(int j=0;j<26;j++)
pos[i][j]=pos[i-1][j];
pos[i][s[i]-'a']=i;
}
printf("%lld
",manacher());
return 0;
}