[BZOJ 2160] 拉拉队排练 Manacher+贪心

题目传送门:【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;否则就输出答案。

下面附上代码:

  1. #include<cstdio>   
  2. #include<cstring>   
  3. #include<iostream>   
  4. #include<algorithm>   
  5. using namespace std;  
  6. const int MX = 1000005;  
  7. const long long MOD = 19930726LL;  
  8.   
  9. int n,mr,id,pal[MX],len = 0;                        //pal:对应位置的回文子串半径   
  10. char ori[MX];                                       //val:对应位置的回文子串长度   
  11. long long geshu[MX],val[MX],maxv = 0,k;             //geshu:各个长度的回文子串的个数   
  12.   
  13. long long mypow(int a,int b){                       //快速幂   
  14.     long long res = 1,base = a;  
  15.     while (b){  
  16.         if (b & 1) res *= base % MOD,res %= MOD;  
  17.         base *= base % MOD;  
  18.         base %= MOD;  
  19.         b >>= 1;  
  20.     }  
  21.     return res % MOD;  
  22. }  
  23.   
  24. void manacher(){  
  25.     for (int i = 1;i <= len;i++){  
  26.         if (i < mr) pal[i] = min(pal[2 * id - i],mr - i);  
  27.         else pal[i] = 1;  
  28.         while (ori[i - pal[i]] == ori[i + pal[i]]){  
  29.             pal[i]++;  
  30.         }  
  31.         if (i + pal[i] > mr) mr = i + pal[i],id = i;  
  32.         val[i] = pal[i] * 2 - 1;  
  33.         if (val[i] > maxv) maxv = val[i];  
  34.         geshu[val[i]]++;                            //对应长度的字符串数量 +1   
  35.     }  
  36. }  
  37.   
  38. long long greed(){                                      //贪心,从大到小计算   
  39.     long long ans = 1;  
  40.     long long left = k,cur = 0;  
  41.     for (int i = maxv;i > 0;i -= 2){  
  42.         geshu[i] += geshu[i + 2];  
  43.     }  
  44.     for (int i = maxv;i > 0;i -= 2){  
  45.         if (left - geshu[i] >= 0){  
  46.             left -= geshu[i];  
  47.             cur = geshu[i];  
  48.         }  
  49.         else{  
  50.             cur = left;  
  51.             left = 0;  
  52.         }  
  53.         ans *= mypow(i,cur)%MOD;  
  54.         ans %= MOD;  
  55.     }  
  56.     if (left > 0) return -1;                     //小于k,返回-1,否则返回ans   
  57.     return ans;  
  58. }  
  59.    
  60. int main(){  
  61.     cin>>n>>k;  
  62.     scanf(”%s”,ori + 1);  
  63.     len = strlen(ori + 1);  
  64.     ori[0] = ’+’;  
  65.     ori[len + 1] = ’-‘;  
  66.     manacher();  
  67.     printf(”%lld”,greed());  
  68.     return 0;  
  69. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值