题目:
字符串中有多少不同的回文子串
准备知识:
字符串hash
https://blog.csdn.net/sodacoco/article/details/83240305
主要运用下面两个式子:
计算H ( C )的过程是递归实现的:H(C,k)为前 k 个字符构成的字符串的哈希值,(若不考虑取模):
H(C,k+1)= H( C , k ) * b+c( k+1 );
a-z 对应 1-26,所以c( k+1 )为 s [ i ] - 'a' +1
主串上 长度为 n 的任意子串 C '=c(k+1) c(k+2) c(k+3) . . . c(k+n) 的哈希值:
H(C ’)= H( C , k+n ) - H(C, k ) * b^n;
一定要注意,字符串从k+1开始,这里减去的是k,不是k+1
马拉车算法
直接堆模板
思路:
先跑一遍马拉车,并且计算字符串hash值(基于原字符串)
遍历每一个中心点,found函数求答案。found函数大体思路:
得到这个点最长回文串的起点st,和终点en,注意要跳过插入的“#”,然后套用公式,求得这个回文子串的hash值,如果没被标记,ans++,并标记;标记了则向中心点缩进,得到新的st,en并重复这个步骤,直到st>en
代码:
#include<bits/stdc++.h>
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
typedef unsigned long long ull;
const int N = 1e5+5;
const int b = 311;///hash 基数
ull has[N],base[N];
int ans=0,p[2*N],len=0;
char s[N], str[2 * N];
int st,en;
map<ull,int>mp;
void init() {
len = strlen(s);
int k = 0;
has[0]=0;
str[k++] = '$';///str【0】
for(int i = 0; i < len; i++) {
has[i+1]=has[i]*b+s[i]-'a'+1;///a-z 1-26,所以加1
str[k++] = '#';
str[k++] = s[i];///一个#,一个字符
}
str[k++] = '#';///str[2*len+1]
len = k;
str[k]='@';///字符串尾,防止越界
}
void manacher() {
int mx = 0, id;///mx为最右边,id为中心点
for(int i = 1; i < len; i++)
{
if(i < mx) p[i] = min(mx - i, p[2 * id - i]);///判断这个点有没有超过mx
else p[i] = 1;///超过就标记为1
while(str[i - p[i]] == str[i + p[i]]) p[i]++;///判断当前点是不是最长回文子串,不断的向右扩展
if(p[i] + i > mx)///p[i]+i 为最右边界
{
mx = p[i] + i;
id = i;///更新中间点
}
}
}///#c#d#b#d#c# id为b时 p【i】=6,包括最中间的b
void found(int id,int dis)
{
st=id-dis+1,en=id+dis-1;
if(st%2==1)st++,en--;///此时为#
while(st<=en)
{
ull k=has[en/2]-has[st/2-1]*base[en/2-st/2+1];///注意字符串是k+1-k+n,减去的是has(c,k)
if(!mp[k])///去重
{
mp[k]=1;
ans++;
}
else break;///长的被标记过,短的肯定也标记了
st+=2,en-=2;///跳过#
}
}
int main()
{
ios;
int n,ca=1;
cin>>n;
base[0]=1;
for(int i=1;i<N;i++)
{
base[i]=base[i-1]*b;
}///b的几次方
while(n--)
{
cin>>s;
init();
ans=0;
mp.clear();
memset(p,0,sizeof(p));
manacher();
for(int i=1;i<len;i++)
{
found(i,p[i]);
}
cout<<"Case #"<<ca<<": "<<ans<<endl;
ca++;
}
return 0;
}