2160: 拉拉队排练
Time Limit: 10 Sec Memory Limit: 259 MBSubmit: 1058 Solved: 411
[ Submit][ Status][ Discuss]
Description
艾利斯顿商学院篮球队要参加一年一度的市篮球比赛了。拉拉队是篮球比赛的一个看点,好的拉拉队往往能帮助球队增加士气,赢得最终的比赛。所以作为拉拉队队长的楚雨荨同学知道,帮助篮球队训练好拉拉队有多么的重要。拉拉队的选拔工作已经结束,在雨荨和校长的挑选下,n位集优秀的身材、舞技于一体的美女从众多报名的女生中脱颖而出。这些女生将随着篮球队的小伙子们一起,和对手抗衡,为艾利斯顿篮球队加油助威。一个阳光明媚的早晨,雨荨带领拉拉队的队员们开始了排练。n个女生从左到右排成一行,每个人手中都举了一个写有26个小写字母中的某一个的牌子,在比赛的时候挥舞,为小伙子们呐喊、加油。雨荨发现,如果连续的一段女生,有奇数个,并且他们手中的牌子所写的字母,从左到右和从右到左读起来一样,那么这一段女生就被称作和谐小群体。现在雨荨想找出所有和谐小群体,并且按照女生的个数降序排序之后,前K个和谐小群体的女生个数的乘积是多少。由于答案可能很大,雨荨只要你告诉她,答案除以19930726的余数是多少就行了。
Input
输入为标准输入。第一行为两个正整数n和K,代表的东西在题目描述中已经叙述。接下来一行为n个字符,代表从左到右女生拿的牌子上写的字母。
Output
输出为标准输出。输出一个整数,代表题目描述中所写的乘积除以19930726的余数,如果总的和谐小群体个数小于K,输出一个整数-1。
Sample Input
ababa
Sample Output
【样例说明】
和谐小群体女生所拿牌子上写的字母从左到右按照女生个数降序排序后为ababa, aba, aba, bab, a, a, a, b, b,前三个长度的乘积为。
HINT
总共20个测试点,数据范围满足:
Source
题解:manacher+前缀和
首先先介绍一下manacher
manacher 可以O(n)的求解最长回文子串。
p[i]表示以i位置的字符为对称中心,向左/右最长能延伸的回文子串的长度。
然后我们开两个辅助变量mx表示1-(i-1)为中心的最长回文子串能延伸到的最远的长度,id表示对称中心的位置下标。
当 mx - i > P[j] 的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于 i 和 j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有 P[i] = P[j],见下图。
当 P[j] > mx - i 的时候,以S[j]为中心的回文子串不完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 P[i] >= mx - i。至于mx之后的部分是否对称,就只能一个一个匹配了。
对于 mx <= i 的情况,无法对 P[i]做更多的假设,只能P[i] = 1,然后再去匹配了
下面给出原文,进一步解释算法为线性的原因
注意如果直接对原串做manacher那么求出的全部都是长度为奇数的子串,所以我们如果要求偶数的就要在原串每两个字符之间加入一个分隔符,然后再求p[i],有一个规律是加入分隔符后的p[i]-1就是原串回文子串的真实长度。
会了manacher这个题就基本解决了。我们用一个前缀和数组来维护回文半径(p[i],不包括对称中心)为x的回文子串的个数。用类似树状数组区间修改点查询的方式,因为假设i的回文半径为p[i],那么相当于长度为1..p[i]长度的回文子串都增加了1个。处理出每个长度的个数,从大到小选取计算答案即可。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define N 1000003
#define LL long long
#define p1 19930726
using namespace std;
int n,dp[N],p[N];
char ch[N];
LL m,sum[N];
void manacher()
{
int mx=0,id;
for (int i=1;i<=n;i++){
if (mx>=i) p[i]=min(mx-i,p[2*id-i]);
else p[i]=0;
for (;ch[i+p[i]+1]==ch[i-p[i]-1];p[i]++);
if (p[i]+i>mx) mx=p[i]+i,id=i;
sum[0]++; sum[p[i]+1]--;
}
}
LL quickpow(LL num,LL x)
{
LL base=num%p1; LL ans=1;
while (x){
if (x&1) ans=(ans*base)%p1;
x>>=1;
base=(base*base)%p1;
}
return ans;
}
int main()
{
freopen("a.in","r",stdin);
freopen("my.out","w",stdout);
scanf("%d%lld",&n,&m);
scanf("%s",ch+1);
ch[0]='#';
manacher();
//for (int i=1;i<=n;i++) cout<<p[i]<<" ";
//cout<<endl;
for (int i=1;i<=n;i++) sum[i]=sum[i-1]+sum[i];
LL ans=1; LL now=0;
for (int i=n/2+1;i>=0;i--) {
if (sum[i]==0) continue;
ans=(ans*quickpow(i*2+1,min(sum[i],m-now)))%p1;
now+=sum[i];
if (now>=m) break;
}
if (now<m) {
printf("-1\n");
return 0;
}
printf("%lld\n",ans);
}