2022牛客多校九 G-Magic Spells(manacher+双哈希)

题目链接:登录—专业IT笔试面试备考平台_牛客网

题目:

 样例输入:

3
abaca
abccaca
acabb

样例输出:

4

题意:首先给定一个n,然后给定n个字符串,求出有多少个字符串满足是回文串而且是n个字符串的子串。

这道题可以用回文树来求解,但我还不会,下面我用manacher加双哈希来写一下这道题目的思路。不懂manacher算法的小伙伴可以看下这里:Manacher(求解最长回文子串)_AC__dream的博客-CSDN博客

首先要理解manacher求解最长回文子串长度的原理,其实manacher是能够处理出来以所有字符为中心的最长回文子串能够扩展的长度,整个字符串的最长回文子串只不过是以每个字符为中心的最长回文子串长度的最大值而已。他的复杂度是O(n)的,也就是说他扩展的次数就是O(n)的,而我们这道题需要预处理出来每个字符串中本质不同的回文子串的个数,由于在manacher算法求解p数组时我们用到了对称性,在这里我们依旧可以用一下对称性,假如以当前字符为中心的回文子串最大扩展长度通过对称性得到了的是x,我们还需要在x的基础上继续扩展,那么说明以当前字符为中心的长度为x的回文子串都已经在前面记录过了,所以肯定不会是与之前本质不同的回文串,我们无需再做记录,我们只需要判断暴力扩展的字符串是否为本质不同的回文串即可,判断方法为双哈希,但是需要注意的是双哈希不能让unsigned long long自然溢出,这道题会卡这个,我双哈希设置了两个模数,一个是1e9+7,另一个是1e9+11,双哈希就是把两个哈希值看作一个pair,并用一个map记录pair的出现次数即可,这样我们就可以求出每个串中本质不同的回文字符串出现的次数,我们存进去一个map,保证每个字符串中回文字符串只会记录进map一次,那么最后我们统计map中每个字符串出现的次数,当出现次数等于n时代表每个字符串都有该子串,计入答案即可。

细节见代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
#include<unordered_map>
using namespace std;
typedef pair<long long,long long>PII;
const int N=6e5+10,mod1=1e9+7,mod2=1e9+11;
map<PII,int>mp;
char s[10][N];
long long h1[10][N],h2[10][N],P1[N],P2[N];
char t[N];
int p[N];
long long ha1(int id,int l,int r)
{
	return (h1[id][r]-(h1[id][l-1]*P1[r-l+1])%mod1+mod1)%mod1;
}
long long ha2(int id,int l,int r)
{
	return (h2[id][r]-(h2[id][l-1]*P2[r-l+1])%mod2+mod2)%mod2;
}
void manacher(int o,char s[])
{
	int n=strlen(s+1);
	for(int i=n;i>=0;i--)
	{
		p[i*2+1]=p[i*2]=0;
		t[i*2+1]='#';
		t[i*2]=s[i];
	}
	map<PII,int>mpp;
	t[0]='$';
	int mx=0,id=0;
	for(int i=1;i<=2*n+1;i++)
	{
		if(i<mx) p[i]=min(p[2*id-i],mx-i);
		else p[i]=1;
		while(i>p[i]&&t[i-p[i]]==t[i+p[i]])
		{
			if(t[i]=='#')//偶数长度 
			{
				int tt=p[i]>>1;
				if(tt==0)
				{
					p[i]++;
					continue;
				}
				int tid=(i-1)/2;//对应于原字符串左中心
				if(!mpp[{ha1(o,tid-tt+1,tid+tt),ha2(o,tid-tt+1,tid+tt)}])
				{
					mpp[{ha1(o,tid-tt+1,tid+tt),ha2(o,tid-tt+1,tid+tt)}]++;
					mp[{ha1(o,tid-tt+1,tid+tt),ha2(o,tid-tt+1,tid+tt)}]++;
				}
			}
			else//奇数长度 
			{
				int tt=(p[i]+1)/2;
				int tid=i/2;
				if(!mpp[{ha1(o,tid-tt+1,tid+tt-1),ha2(o,tid-tt+1,tid+tt-1)}])
				{
					mpp[{ha1(o,tid-tt+1,tid+tt-1),ha2(o,tid-tt+1,tid+tt-1)}]++;
					mp[{ha1(o,tid-tt+1,tid+tt-1),ha2(o,tid-tt+1,tid+tt-1)}]++;
				}
			}
			p[i]++;
		}
		if(i+p[i]>mx)
		{
			mx=i+p[i];
			id=i;
		}
	}
}
int main()
{
	int T;
	P1[0]=P2[0]=1;
	for(int i=1;i<N;i++) P1[i]=(P1[i-1]*131)%mod1,P2[i]=(P2[i-1]*13331)%mod2;
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s[i]+1);
		for(int j=1;j<=strlen(s[i]+1);j++)
		{
			h1[i][j]=(h1[i][j-1]*131+s[i][j])%mod1;
			h2[i][j]=(h2[i][j-1]*13331+s[i][j])%mod2;
		}
		manacher(i,s[i]);
	}
	long long ans=0;
	for(map<PII,int>::iterator it=mp.begin();it!=mp.end();it++)
		if(it->second==n) ans++;
	printf("%lld",ans);
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值