题目描述
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;
}