[manacher][hash]Magic Spells 2022牛客多校第9场 G

22 篇文章 0 订阅
3 篇文章 0 订阅

题目描述

One day in the magic world, the young wizard RoundDog was learning the compatibility of spells. After experimenting for a long time, he reached the conclusion that the compatibility of a spell collection can be measured by the number of distinct palindromes that are substrings of all the spells in the collection. He was so excited and planned to write a program to calculate the compatibility of any input spell collection.

However, RoundDog was busy participating the NowWizard Multi-Universe Training this week. He was too struggling during the competition and feels tired now.

Since RoundDog is not in the mood to finish the program now, could you help him?

输入描述:

The first line contains a single integer kkk (1≤k≤5)(1 \leq k \leq 5)(1≤k≤5), indicating the number of spells in a spell collection.

In the following kkk lines, each line contains a spell SSS (1≤∣S∣≤300 000)(1 \le |S| \le 300\,000)(1≤∣S∣≤300000), which is a string containing only lowercase English letters.

It is guaranteed that the total length of the spells does not exceed 300 000300\,000300000.

输出描述:

Output the compatibility of the input spell collection, which is the number of distinct palindromes that are substrings of all the spells in the collection.

示例1

输入

3
abaca
abccaca
acabb

输出

4

说明

In the example, "a", "b", "c", "aca" are the four distinct palindromes that are substrings of all the input spells.

题意: 给出若干字符串,求这些字符串中出现的回文子串个数,要求每一个回文子串必须在所有的字符串中都出现过。

分析: 做这道题需要知道一个字符串本质上不同的回文子串一共大约有字符串长度个,如果能求出每个字符串所有本质上不同的回文子串,那这道题目就很容易解决了,只需要把它们丢进一个map里,统计一下出现次数等于n的字符串数,这就是答案。而求出一个字符串所有本质不同的回文子串可以通过回文自动机或者manacher,由于本人能力有限就只说下后者。在manacher中心扩展的时候一定会遍历到所有的本质不同的回文子串,因为继承镜像位置的扩展长度并不会产生新的回文子串,所有新的回文子串都需要通过中心扩展得到,而中心扩展的次数平均为len级别的,所以通过manacher就能够O(n)找到所有本质不同的回文子串,对于每个字符串都跑一遍manacher,由于直接往map中丢string可能会T,所以可以丢字符串的哈希值,而单哈希是很容易被卡的,所以这道题要用双哈希。

具体代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <unordered_map>
#include <map>
#define pii pair<long long, long long>
using namespace std;

map<pii, int> mp;
int p[700010];//p[i]记录s[i]能单侧延伸多长 
char s[700010];
long long P1[700010];
long long P2[7000010];
const int PP1 = 233;
const int PP2 = 173;
long long h1[700010];
long long h2[700010];
const int mod1 = 1e9+9; 
const int mod2 = 1e9+11;

long long _hash1(int l, int r){
	return ((h1[r]-h1[l-1]*P1[r-l+1]%mod1)%mod1+mod1)%mod1; 
} 

long long _hash2(int l, int r){
	return ((h2[r]-h2[l-1]*P2[r-l+1]%mod2)%mod2+mod2)%mod2; 
} 

int manacher()
{
	map<pii, int> mpp;
	int r = 0;//最右边界
	int c = 0;//当前中心 
	int len = strlen(s)-1, ans = 0;
	for(int i = 1; i <= len; i++){
		if(s[i] == '#') continue;
		if(!mpp.count(make_pair(s[i], s[i])))
			mpp[make_pair(s[i], s[i])]++;
	}
	for(int i = 1; i <= len; i++)
	{
		if(i < r)
			p[i] = min(r-i, p[2*c-i]);//点i关于c的对称点2*c-i
		else
			p[i] = 1;//单个字符延伸长度为1
		while(s[i+p[i]] == s[i-p[i]]){
			p[i]++;
			if(s[i+p[i]-1] == '#') continue;
			long long t1 = _hash1(i-p[i]+1, i+p[i]-1);
			long long t2 = _hash2(i-p[i]+1, i+p[i]-1);
			if(!mpp.count(make_pair(t1, t2)))
				mpp[make_pair(t1, t2)]++;
		} 
		if(i+p[i] > r)//更新中心点及最右边界
		{
			r = i+p[i];
			c = i;
		}
		ans = max(ans, p[i]);
	}
	for(map<pii, int>::iterator it = mpp.begin(); it != mpp.end(); it++)
		mp[it->first]++;
	return ans-1;//当前位置的p[i]-1就是该位置能构成的最长回文串
} 

signed main()
{
	P1[0] = 1;
	for(int i = 1; i <= 700000; i++) 
		P1[i] = P1[i-1]*PP1%mod1;
	P2[0] = 1;
	for(int i = 1; i <= 700000; i++) 
		P2[i] = P2[i-1]*PP2%mod2;
	int n;
	cin >> n;
	getchar();
	for(int i = 1; i <= n; i++){
		char ch;
		int cnt = 0;
		while(isalpha(ch = getchar()))
		{
			s[++cnt] = '#';
			s[++cnt] = ch;
		}
		s[0] = '%';
		s[++cnt] = '#';
		s[++cnt] = '\0';
		for(int i = 1; i <= cnt-1; i++)
			h1[i] = (h1[i-1]*PP1+s[i])%mod1;
		for(int i = 1; i <= cnt-1; i++)
			h2[i] = (h2[i-1]*PP2+s[i])%mod2;
		manacher();
	}
	long long ans = 0;
	for(map<pii, int>::iterator it = mp.begin(); it != mp.end(); it++)
		if(it->second == n)
			ans++;
	cout << ans;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值