[NOI 2016] 优秀的拆分
[题目链接]
https://www.lydsy.com/JudgeOnline/problem.php?id=4650
[算法]
首先 , 求形如"AABB"的子串个数 , 我们只要预处理 :
Fi : 以i为最后一个字符的所有子串中 , 有多少个“AA"串
Gi :以i为第一个字符的所有子串中 , 有多少个"BB"串
那么 , 第i个位置对答案的贡献即为Fi * Gi+1 , 贡献相加为答案
用字符串哈希暴力预处理F和G , 可以拿到95分
那么 , 怎样拿到100分呢?
不难发现 , 其实我们只要能求出形如"AA"的子串 , 问题就解决了
不妨枚举长度L , 为”AA"长度的一半 , 每个下标为L倍数的位置我们称之为“关键点”
每次枚举相邻两个关键点i , j , 求出 :
lcp = LCP(pre(i) , pre(j))
lcs = LCS(Suf(i - 1) , suf(j - 1))
若lcp + lcs >= L , 那么就对中间的一段区间产生了“贡献” , 可以画图理解
可以差分前缀和 + 字符串哈希解决
时间复杂度 : O(NlogN ^ 2) (调和级数 : n / 1 + n / 2 + .. + n / n = nlogn)
[代码]
#include<bits/stdc++.h> using namespace std; const int N = 30010; typedef long long ll; typedef long double ld; typedef unsigned long long ull; const int P = 3e7 + 7; int n; ll pref[N] , suf[N]; int H[N] , pw[N]; char s[N]; template <typename T> inline void read(T &x) { T f = 1; x = 0; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0'; x *= f; } inline int gethash(int l , int r) { int ret = (H[r] - 1ll * H[l - 1] * pw[r - l + 1] % P) % P; if (ret < 0) ret += P; ret %= P; return ret; } int main() { int T; read(T); while (T--) { scanf("%s" , s + 1); n = strlen(s + 1); memset(pref , 0 , sizeof(pref)); memset(suf , 0 , sizeof(suf)); memset(H , 0 , sizeof(H)); memset(pw , 0 , sizeof(pw)); pw[0] = 1; for (int i = 1; i <= n; i++) pw[i] = 1ll * pw[i - 1] * 131 % P; for (int i = 1; i <= n; i++) H[i] = (1ll * H[i - 1] * 131 + s[i] - 'a' + 1) % P; for (int L = 1; L <= n; L++) { for (int i = L; i + L <= n; i += L) { int j = i + L; int l = 1 , r = L - 1 , lcs = 0 , lcp = 0; while (l <= r) { int mid = (l + r) >> 1; if (gethash(j - mid , j - 1) == gethash(i - mid , i - 1)) { lcs = mid; l = mid + 1; } else r = mid - 1; } l = 1 , r = L; while (l <= r) { int mid = (l + r) >> 1; if (gethash(i , i + mid - 1) == gethash(j , j + mid - 1)) { lcp = mid; l = mid + 1; } else r = mid - 1; } if (lcp + lcs < L) continue; int t = lcp + lcs - L + 1; ++pref[j + lcp - t]; --pref[j + lcp]; ++suf[i - lcs]; --suf[i - lcs + t]; } } ll ans = 0; for (int i = 1; i <= n; i++) pref[i] += pref[i - 1]; for (int i = 1; i <= n; i++) suf[i] += suf[i - 1]; for (int i = 1; i < n; i++) ans += 1ll * pref[i] * suf[i + 1]; printf("%lld\n" , ans); } return 0; }