BZOJ 2160 拉拉队排练 (Manacher 序列差分)
2160: 拉拉队排练
Time Limit: 10 Sec Memory Limit: 259 MB
Description
艾利斯顿商学院篮球队要参加一年一度的市篮球比赛了。拉拉队是篮球比赛的一个看点,好的拉拉队往往能帮助球队增加士气,赢得最终的比赛。所以作为拉拉队队长的楚雨荨同学知道,帮助篮球队训练好拉拉队有多么的重要。拉拉队的选拔工作已经结束,在雨荨和校长的挑选下,n位集优秀的身材、舞技于一体的美女从众多报名的女生中脱颖而出。这些女生将随着篮球队的小伙子们一起,和对手抗衡,为艾利斯顿篮球队加油助威。一个阳光明媚的早晨,雨荨带领拉拉队的队员们开始了排练。n个女生从左到右排成一行,每个人手中都举了一个写有26个小写字母中的某一个的牌子,在比赛的时候挥舞,为小伙子们呐喊、加油。雨荨发现,如果连续的一段女生,有奇数个,并且他们手中的牌子所写的字母,从左到右和从右到左读起来一样,那么这一段女生就被称作和谐小群体。现在雨荨想找出所有和谐小群体,并且按照女生的个数降序排序之后,前K个和谐小群体的女生个数的乘积是多少。由于答案可能很大,雨荨只要你告诉她,答案除以19930726的余数是多少就行了。
Input
输入为标准输入。第一行为两个正整数n和K,代表的东西在题目描述中已经叙述。接下来一行为n个字符,代表从左到右女生拿的牌子上写的字母。
Output
输出为标准输出。输出一个整数,代表题目描述中所写的乘积除以19930726的余数,如果总的和谐小群体个数小于K,输出一个整数-1。
Sample Input
5 3
ababa
Sample Output
45
【样例说明】
和谐小群体女生所拿牌子上写的字母从左到右按照女生个数降序排序后为ababa, aba, aba, bab, a, a, a, b, b,前三个长度的乘积为。
HINT
总共20个测试点,数据范围满足:
题目大意:给出一个长度为n的字符串,求最长的k个回文子串(此题中的回文子串指长为奇数的回文子串)长度的乘积。模19930726后输出,若没有k个则输出-1。
思路:
首先可以用manacher算法处理处每个位置的最长回文串, 由于长度是奇数,所以不需要在原串中插入字符。
考虑每一个最长的回文串,都可以通过两端减去相同数目的字符形成新的回文串,而我们要统计每种长度的串有几个,考虑一个长度为2 * i - 1的极长字符串,它会贡献出长度为1,3,5…2 * i - 1的回文串。
于是统计一个极长回文串的贡献就是一个区间加问题,考虑差分,每次更新[1, p[i]]这个区间 + 1(cnt[i]表示长度为2 * i - 1的回文串数量) , 直接每次更新的时候q[1]++, q[p[i] + 1]–, 最后后q[1~i]的和就是最终cnt[i]的值。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#define LL long long
using namespace std;
const int N = 1000010;
const LL mod = 19930726;
char s[N];
int n, p[N];
LL k, q[N], cnt[N];
LL power(LL aa, LL bb) {
LL ans = 1;
while( bb ){
if(bb & 1) ans = (ans * aa) % mod;
bb >>= 1;
aa = aa * aa % mod;
}
return ans;
}
void manacher(){
memset(p, 0, sizeof(p));
int id = 0, mx = 0;
for(register int i=1; i<=n; i++){
if(mx > i) p[i] = min(p[id*2-i], mx-i+1);
else p[i] = 1;
while(s[i+p[i]] == s[i-p[i]]) p[i]++;
if(i+p[i]-1 > mx){
mx = i+p[i]-1;
id = i;
}
q[1]++; //半径为1~p[i]的回文串都多一个
q[p[i] + 1]--; //序列差分
}
}
int main(){
scanf("%d %lld", &n, &k);
scanf("%s", s + 1);
s[0] = '+'; s[n + 1] = '-';
memset(q, 0, sizeof(q));
manacher();
memset(cnt, 0, sizeof(cnt));
int mx = 0;
for(register int i=1; i<=(n+1)>>1; i++){//cnt[i]表示长度为2*i-1的串的个数
cnt[i] = cnt[i - 1] + q[i];
if(cnt[i] > 0) mx = i;//最长串的长度
}
LL ans = 1;
int r = mx;
while(r && k>0){
ans = (ans * power((LL)(2*r-1), min(cnt[r], k))) % mod;//最长串长度乘上需要的个数
//k -= min(cnt[r], k); r--;
k -= cnt[r]; r--;
}
if( k>0 ) printf("-1\n");//统计完所有的回文串也达不到k个
else printf("%lld\n", ans);
return 0;
}