好像很简单的样子。
考虑能不能直接求,感觉有点麻烦。因为要考虑右端点在当前回文子串内还有区间包含问题。
那么考虑补集转化。问题转化成,考虑当前的回文串,之前有多少个回文串与它不相交。
跑一遍Manacher。然后先差分再二阶前缀和求出以第$i$个位置为右端点的回文子串的个数。然后再求一次前缀和就可以了。
然后再扫一遍就做完了。
Code
1 /** 2 * Codeforces 3 * Problem#17E 4 * Accepted 5 * Time: 186ms 6 * Memory: 37200k 7 */ 8 #include <bits/stdc++.h> 9 using namespace std; 10 typedef bool boolean; 11 12 const int N = 2e6 + 5, M = 51123987, inv2 = (M + 1) >> 1; 13 14 int add(int a, int b) { 15 return ((a += b) >= M) ? (a - M) : (a); 16 } 17 18 int sub(int a, int b){ 19 return ((a -= b) < 0) ? (a + M) : (a); 20 } 21 22 int n, m; 23 char s[N], str[N << 1]; 24 int mxr[N << 1]; 25 int f[N]; 26 27 inline void init() { 28 scanf("%d", &n); 29 scanf("%s", s + 1); 30 } 31 32 void Manacher() { 33 int R = 0, cen = 0; 34 str[0] = '+'; 35 for (int i = 1; i <= n; i++) { 36 str[++m] = s[i]; 37 if (i != n) 38 str[++m] = '#'; 39 } 40 str[++m] = '-'; 41 42 for (int i = 1; i < m; i++) { 43 if (i < R) 44 mxr[i] = min(R - i, mxr[(cen << 1) - i]); 45 while (str[i - mxr[i]] == str[i + mxr[i]]) 46 mxr[i]++; 47 if (i + mxr[i] > R) 48 R = i + mxr[i], cen = i; 49 } 50 } 51 52 inline void solve() { 53 Manacher(); 54 for (int i = 1; i < m; i++) 55 f[(i >> 1) + 1]++, f[((i + mxr[i]) >> 1) + 1]--; 56 for (int i = 2; i <= n; i++) 57 f[i] = add(f[i], f[i - 1]); 58 for (int i = 2; i <= n; i++) 59 f[i] = add(f[i], f[i - 1]); 60 int res = sub((f[n] * 1ll * f[n]) % M, f[n]) * 1ll * inv2 % M; 61 for (int i = 2; i <= n; i++) 62 f[i] = add(f[i], f[i - 1]); 63 f[0] = 0, f[n + 1] = 0; 64 65 for (int i = 1; i <= n; i++) { 66 res = sub(res, sub(f[i - 1], f[max(i - ((mxr[(i - 1) << 1 | 1] + 1) >> 1) - 1, 0)])); 67 if (i < n) 68 res = sub(res, sub(f[i - 1], f[max(i - (mxr[i << 1] >> 1) - 1, 0)])); 69 } 70 printf("%d ", res); 71 } 72 73 int main() { 74 init(); 75 solve(); 76 return 0; 77 }