题目传送门:【BZOJ 2160】
题目大意:……拉拉队的选拔工作已经结束,在雨荨的挑选下,n位集优秀的身材、舞技于一体的美女从众多报名的女生中脱颖而出。一个阳光明媚的早晨,雨荨带领拉拉队的队员们开始了排练。这n个女生从左到右排成一行,每个人手中都举了一个写有26个小写字母中的某一个的牌子,在比赛的时候挥舞,为小伙子们呐喊、加油。
雨荨发现,如果连续的一段女生,有奇数个,并且他们手中的牌子所写的字母从左到右和从右到左读起来一样,那么这一段女生就被称作和谐小群体。现在雨荨想找出所有和谐小群体,并且按照女生的个数降序排序之后,前 K 个和谐小群体的女生个数的乘积是多少。由于答案可能很大,雨荨只要你告诉她,答案除以 19930726 的余数是多少就行了。
输入的第一行为两个正整数 n 和 K,代表的东西在题目描述中已经叙述。接下来一行为n个字符,代表从左到右女生拿的牌子上写的字母。
输出为一个整数,代表题目描述中所写的乘积除以 19930726 的余数,如果总的和谐小群体个数小于K,输出一个整数-1。( n ≤ 106 ,k ≤ 1012 )
题目分析:
题目看起来很长,不过压缩后,就是求最长的 k 个回文子串长度的乘积。求出回文子串的长度,直接使用 Manacher 即可,同时统计每个长度出现的次数(O( n )),存放到一个新的值域数组中。之后,再从大到小贪心地选择这些数相乘并取模,就能得到答案。
例如,对于一个字符串 ababcba,我们跑完一遍 Manacher 之后,将它们的回文长度存在新的数组 val 中,此时 val[]={ 0 , 7 , 0 , 3 , 0 , 1 , 0 , 0 , …… },意为:长度为 1 的回文子串有 7 个,长度为 3 的回文子串有 3 个,长度为 5 的回文子串有 1 个(长度为偶数的不予考虑)。然后根据 k 的大小从最长的长度开始依次往下乘。如果取的次数小于 k,那么说明不符合情况,直接输出 -1;否则就输出答案。
下面附上代码:
- #include<cstdio>
- #include<cstring>
- #include<iostream>
- #include<algorithm>
- using namespace std;
- const int MX = 1000005;
- const long long MOD = 19930726LL;
- int n,mr,id,pal[MX],len = 0; //pal:对应位置的回文子串半径
- char ori[MX]; //val:对应位置的回文子串长度
- long long geshu[MX],val[MX],maxv = 0,k; //geshu:各个长度的回文子串的个数
- long long mypow(int a,int b){ //快速幂
- long long res = 1,base = a;
- while (b){
- if (b & 1) res *= base % MOD,res %= MOD;
- base *= base % MOD;
- base %= MOD;
- b >>= 1;
- }
- return res % MOD;
- }
- void manacher(){
- for (int i = 1;i <= len;i++){
- if (i < mr) pal[i] = min(pal[2 * id - i],mr - i);
- else pal[i] = 1;
- while (ori[i - pal[i]] == ori[i + pal[i]]){
- pal[i]++;
- }
- if (i + pal[i] > mr) mr = i + pal[i],id = i;
- val[i] = pal[i] * 2 - 1;
- if (val[i] > maxv) maxv = val[i];
- geshu[val[i]]++; //对应长度的字符串数量 +1
- }
- }
- long long greed(){ //贪心,从大到小计算
- long long ans = 1;
- long long left = k,cur = 0;
- for (int i = maxv;i > 0;i -= 2){
- geshu[i] += geshu[i + 2];
- }
- for (int i = maxv;i > 0;i -= 2){
- if (left - geshu[i] >= 0){
- left -= geshu[i];
- cur = geshu[i];
- }
- else{
- cur = left;
- left = 0;
- }
- ans *= mypow(i,cur)%MOD;
- ans %= MOD;
- }
- if (left > 0) return -1; //小于k,返回-1,否则返回ans
- return ans;
- }
- int main(){
- cin>>n>>k;
- scanf(”%s”,ori + 1);
- len = strlen(ori + 1);
- ori[0] = ’+’;
- ori[len + 1] = ’-‘;
- manacher();
- printf(”%lld”,greed());
- return 0;
- }