[manacher/二分+哈希]KFC Crazy Thursday 2022牛客多校第5场 G

题目描述

One day, NIO found an email on his computer from his friend Kala. He opened the email and found a picture with a large string of 26 lowercase letters. He asked Kala why he had sent him this picture. Kala said it was a challenge he had given to NIO: if NIO could figure out the number of palindromes end with 'k', 'f' and 'c' , he would buy NIO a KFC combo. The clever NIO turned on a AI software and converted all the letters on the image into a text file. NIO promised that he will share the KFC combo with you if you can help him. 

输入描述:

There will be multiple test cases.

The first line a number NNN, denoting the length of the string.

The second line is a string consists of lower letters 'a' to 'z'.

1≤N≤5×1051\leq N\leq 5\times 10^51≤N≤5×105

输出描述:

A line with 333 numbers, denoting the number of palindromes, that end with 'k', 'f' and 'c'.

示例1

输入

6
kfccfk

输出

3 3 3

说明

For the first 'k', 1 palindrome.

For the second 'k', 2 palindromes.

For the first 'f', 1 palindrome.

For the second 'f', 2 palindromes.

For the first 'c', 1 palindrome.

For the second 'c', 2 palindromes.

备注:

Palindromes with length greater or equal to one is considered. For example, 'k' is a palindrome. 

题意: 给出一个长度为n的字符串,分别求以'k'、'f'和'c'结尾的回文子串个数。

分析: 听说这题是道回文自动机模板题,但是本人太菜了,只会另外两种方法,一种是二分+哈希,时间复杂度为O(nlogn),一种是manacher算法,时间复杂度为O(n)。由于二分+哈希思路比较明显而且代码也很好写,就只说下解题思路,以'k'为例,首先需要维护出来前i个字符中有多少个'k'出现,这点是为了后面快速得到区间内'k'的出现次数,之后的步骤就和求最长回文子串一样了,O(n)枚举回文中心,对于每个中心O(logn)二分得到最长回文长度,然后这个中心位置对于答案的贡献就是最长回文串中'k'出现次数+1再除2,为什么要+1?这主要是考虑到中心位置字符本身就是'k'的情况。

第二种方法是manacher算法,这个算法一般用来求最长回文子串长度,但是这里对其稍加修改也可以解决该问题。我们知道manacher算法是一个优化版的中心扩展法,同时每个位置都有一个最远延伸长度p[i],另外还需要维护一个最远回文串的中心以及最右边界r,当前位置p[i]的值是可以通过前面某个镜像的位置更新的,所以需要维护一个数组,记录下来第i个位置扩展区间内合法的子串数,之后再以中心扩展法暴力匹配时,也可以记录下来匹配的'k'个数,二者加和就是当前位置扩展区间内合法的子串数,但是其实还有一个问题,那就是镜像点扩展长度超出了最右边界r,此时当前位置的扩展长度与镜像点扩展长度p[i]并不相同了,镜像点的p[i]范围更大,其中记录的合法子串可能根本不会在当前位置出现,所以对于超出最右边界的那些点还需要枚举一遍,要减掉这时候出现的合法子串,最终答案就是每个位置的合法子串数加和。

具体代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#define int long long
using namespace std;

int p[4000005];//p[i]记录s[i]能单侧延伸多长 
char s[4000005];
int numk[4000005], numf[4000005], numc[4000005];

int manacher(){
	int r = 0;//最右边界
	int c = 0;//当前中心 
	int len = strlen(s)-1, ans = 0;
	for(int i = 1; i <= len; i++){
		if(i < r){
			p[i] = min(r-i, p[2*c-i]);//点i关于c的对称点2*c-i
			numk[i] += numk[2*c-i];
			numf[i] += numf[2*c-i];
			numc[i] += numc[2*c-i];
			if(r-i < p[2*c-i]){
				for(int j = r-i; j <= p[2*c-i]; j++){
					if(s[2*c-i+j] == s[2*c-i-j]){
						if(s[2*c-i+j] == 'k')
							numk[i]--;
						if(s[2*c-i+j] == 'f')
							numf[i]--;
						if(s[2*c-i+j] == 'c')
							numc[i]--;
					}
				}
			}
		}
		else
			p[i] = 1;//单个字符延伸长度为1
		while(s[i+p[i]] == s[i-p[i]]){//中心扩展法,根据需要加条件 
			if(s[i+p[i]] == 'k')
				numk[i]++; 
			if(s[i+p[i]] == 'f')
				numf[i]++; 
			if(s[i+p[i]] == 'c')
				numc[i]++; 
			p[i]++;
		}
		if(i+p[i] > r){//更新中心点及最右边界
			r = i+p[i];
			c = i;
		}
		ans = max(ans, p[i]);
	}
	return ans-1;//当前位置的p[i]-1就是该位置能构成的最长回文串
} 

signed main(){
	//预处理s,处理为%#a#b#c#这样形式 
	int n;
	while(~scanf("%lld", &n)){
		getchar();
		char ch;
		int cnt = 0;
		for(int i = 0; i <= 4*n; i++)
			numk[i] = numc[i] = numf[i] = 0; 
		while(isalpha(ch = getchar())){
			s[++cnt] = '#';
			s[++cnt] = ch;
		}
		s[0] = '%';
		s[++cnt] = '#';
		s[++cnt] = '\0';
		manacher();
		int ansk = 0, ansf = 0, ansc = 0;
		for(int i = 0; i <= cnt; i++){
			ansk += numk[i];
			ansf += numf[i];
			ansc += numc[i];
			if(s[i] == 'k') ansk++;
			if(s[i] == 'f') ansf++;
			if(s[i] == 'c') ansc++;
		}
		printf("%lld %lld %lld\n", ansk, ansf, ansc);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值