提交地址:https://nanti.jisuanke.com/t/41389
题目:
给出一个字符串,求字符串中所有回文串的不同字母数之和
解题思路:
建立回文树,,表示在节点cur表示的最长回文子串左右两边加上字符'a'+c构成了新的最长回文子串,对应节点编号为now。
在统计每个最长回文子串内的不同字母数时,参照上图的回文树,从0/1节点向下走一条路径,比如0->6,那么以s[6-1]为结尾的最长回文子串的不同字母数为1,如0->6->7,那么以s[7-1]为结尾的最长回文子串的不同字母数为2。一个最长回文子串中的最大字母数为26,可以用二进制来统计。
具体的统计方法(在建树时完成统计): 式子中的变量和
中的变量相对应,最终对于节点i表示的最长回文子串的不同字母数,就可以通过
中的1的个数得知。
小技巧:求int型的数 对应的二进制中1的个数,可直接调用内置函数__builtin_popcount(x)得到
ac代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 300005;
typedef long long ll;
int fail[maxn], cnt[maxn], num[maxn], len[maxn], nxt[maxn][26], s[maxn];
int last, n, p;
char ss[maxn];
int dif[maxn]={0};
int newnode(int l) //l是节点对应的最长回文子串的长度
{
for(int i = 0; i < 26; i++)
nxt[p][i] = 0;
len[p] = l;
cnt[p] = 0;
num[p] = 0;
return p++;
}
void init()
{
p = 0, last = 0, n = 0, s[0] = -1;
newnode(0); // p=0的初始化
newnode(-1); // p = 1的初始化,之后 p = 2
fail[0] = 1;
fail[1] = 0;
}
int get_fail(int x)
{
while(s[n - len[x] - 1] != s[n])
x = fail[x];
return x;
}
void insert(int c) // c = ch - 'a'
{
s[++n] = c;
int cur = get_fail(last);
if(!nxt[cur][c])
{
int now = newnode(len[cur] + 2); //now表示当前节点编号
fail[now] = nxt[get_fail(fail[cur])][c];//此句和下面一句顺序一定不能倒!!
//因为nxt初始化为0,若当前节点表示的回文串是它自己,那么它的fail应该指向0,此时cur=1,get_fail(fail[cur])=1
nxt[cur][c] = now;
num[now] = num[fail[now]] + 1; //1->n所能形成的回文串数目+1
dif[now] = dif[cur] | (1 << c);
}
last = nxt[cur][c];//当前节点编号
cnt[last]++;//以last为结尾的最长回文子串对应的数目+1
}
void count()
{
for(int i = p - 1; i >= 0; i--) //从后往前更新
cnt[fail[i]] += cnt[i];
}
int main()
{
//freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
init();
scanf("%s", ss+1);//为了避免MLE,也可以边读字符边insert
int lenn = strlen(ss+1);
for(int i = 1; i <= lenn; i++)
insert(ss[i] - 'a');
count();
ll ans = 0;
for(int i = 2; i < p; i++)
ans += 1LL * cnt[i] * __builtin_popcount(dif[i]);
printf("%lld\n", ans);
return 0;
}