[国家集训队]拉拉队排练(Manacher)

传送门

题意

题意其实很简单,就是给你一个字符串让你求所有长度为奇数的回文串从大到小排序前k大的串的长度的乘积。

题解

先跑一遍 $Manacher$,由于是奇数所以直接在原串上跑即可。我们发现对于一个中心 $i$ 和其最长回文半径 $p_i$,所有以 $i$ 为中心,半径小于 $p_i$ 的串也是回文串。

所以我们只需对每个中心最长的回文串的长度的次数+1,然后做一遍后缀和即可求出每个长度的回文串出现的次数。(当然我们只用统计奇数长度的。)

之后从大到小枚举这些长度,如果 $当前k geq 当前回文串长度出现次数$,说明当前长度的回文串一定都在前k个里,设当前长度为len,答案直接加上 $len^{len出现的次数}$, k减去len出现的次数。

否则当前长度的串只有k个符合题目要求,答案只能加上 $len^k$,k直接置位0。

如果所有长度都遍历完了k的值还大于0,则输出-1。

 1 #include <iostream>
 2 #include <cstdio>
 3 using namespace std;
 4 typedef long long ll;
 5 const int N = 2000010;
 6 const int mod = 19930726;
 7 ll n, k;
 8 char s[2000010], ss[1000010];
 9 int top;
10 ll p[2000010], cnt[2000010], sum, ans = 1;
11 ll ksm(ll a, ll b) {
12     ll ret = 1;
13     while (b) {
14         if (b & 1) ret = (ret * a) % mod;
15         a = (a * a) % mod;
16         b >>= 1;
17     }
18     return ret;
19 }
20 void Manacher() {
21     ll mx = 0, id = 0;
22     for (int i = 1; i <= top; i++) {
23         p[i] = ((mx > i) ? min(mx - i, p[id * 2 - i]) : 1);
24         while (s[i + p[i]] == s[i - p[i]]) p[i]++;
25         if (i + p[i] > mx) {
26             mx = i + p[i];
27             id = i;
28         }
29         cnt[p[i] - 1]++;
30     }
31 }
32 int main() {
33     cin >> n >> k;
34     scanf("%s", ss + 1);
35     s[++top] = '$';
36     for (int i = 1; i <= n; i++) {
37         s[++top] = '#';
38         s[++top] = ss[i];
39     }
40     s[++top] = '#';
41     Manacher();
42     for (int i = n; i >= 1; i--) {
43         if (i % 2) {
44             sum += cnt[i];
45             if (sum <= k) {
46                 ans = (ans * ksm(i, sum)) % mod;
47                 k -= sum;
48             } else {
49                 ans = (ans * ksm(i, k)) % mod;
50                 k -= sum;
51                 break;
52             }
53         }
54     }
55     if (k > 0) cout << -1;
56     else cout << ans;
57     return 0;
58 } 
原文地址:https://www.cnblogs.com/zcr-blog/p/12558189.html