HDU 3948 马拉车+hash+map

题目:

字符串中有多少不同的回文子串

准备知识:

字符串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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值