Colorful String【回文串数量】

题意:

给定一个字符串,要求找出所有回文子串中不同字母的个数的总和。
题目链接: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] = '\0';
    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\n",manacher());
    return 0;
}

posted @ 2020-07-01 14:30  xzx9  阅读(111)  评论(0编辑  收藏  举报