洛谷 P7114 / LOJ 3387 「NOIP2020」字符串匹配
思路
令 \(|S| = n\)。首先求出 \(S\) 的 \(\mathbf{Z}\) 函数数组 \(nxt\),考虑将 \(AB\) 视作一个整体的循环节,那么长度为 \(i\) 的循环节最多可以接上 \(\left\lfloor\dfrac{nxt_{i+1}}{i}\right\rfloor + 1\) 组。
这个理解起来很简单。根据定义,子串 \([i+1,i+nxt_{i+1}]\) 与 \([1,nxt_{i+1}]\) 相同,即 \([1,i]\) 与 \([i+1,2i]\) 相同,\([i+1,2i]\) 与 \([2i+1,3i]\),相同,以此类推。
然后考虑题中 \(F(A) \le F(C)\) 的限制。枚举 \(AB\) 的长度,记 \(f(i,j)\) 为 \([i,j]\) 中出现次数为奇数的字符数量。令循环节出现次数为 \(t\),发现只用考虑 \(t\) 为奇数或偶数的情况,因为 \(C(AB)^x\) 与 \(C(AB)^{x+2}\) 的字符出现次数的奇偶情况相同。设 \(|A| = j\ (j < i)\),当 \(t\) 为奇数,则 \(j\) 可行当且仅当 \(f(1,j) \le f(i+1,n)\);当 \(t\) 为偶数,则 \(j\) 可行当且仅当 \(f(1,j) \le f(2i+1,n) \iff f(1,j) \le f(1,n)\)。用树状数组维护当前所有前缀 \([1,k]\ (k < i)\) 中出现次数为奇数的字符数量为 \(x\) 的前缀个数。易知 \(t\) 为奇数时有 \(t - \left\lfloor\dfrac{t}{2}\right\rfloor\) 种情况,为偶数时有 \(\left\lfloor\dfrac{t}{2}\right\rfloor\) 种情况,乘法原理计算即可。
时间复杂度 \(O(Tn \log 26)\)。实现细节见代码。
代码
code
/*
p_b_p_b txdy
AThousandMoon txdy
AThousandSuns txdy
hxy txdy
*/
#include <bits/stdc++.h>
#define pb push_back
#define fst first
#define scd second
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pii;
const int maxn = 1050000;
int n, nxt[maxn], c[30], cnt1[30], cnt2[30];
char s[maxn];
int lowbit(int x) {
return x & (-x);
}
void update(int x, int d) {
for (int i = x; i <= 27; i += lowbit(i)) {
c[i] += d;
}
}
int query(int x) {
int res = 0;
for (int i = x; i; i -= lowbit(i)) {
res += c[i];
}
return res;
}
void solve() {
memset(c, 0, sizeof(c));
memset(cnt1, 0, sizeof(cnt1));
memset(cnt2, 0, sizeof(cnt2));
scanf("%s", s + 1);
n = strlen(s + 1);
// 求 Z 函数
nxt[1] = n;
for (int i = 2, l = 0, r = 0; i <= n; ++i) {
nxt[i] = (i > r) ? 0 : min(nxt[i - l + 1], r - i + 1);
while (s[nxt[i] + 1] == s[nxt[i] + i]) {
++nxt[i];
}
if (i + nxt[i] - 1 > r) {
l = i;
r = i + nxt[i] - 1;
}
}
// 由于 C 非空,所以空一个位置给 C
for (int i = 1; i <= n; ++i) {
if (i + nxt[i] - 1 == n) {
--nxt[i];
}
}
for (int i = 1; i <= n; ++i) {
++cnt2[s[i] - 'a'];
}
// 维护当前前缀和后缀中出现次数为奇数的字符个数
int pre = 0, suf = 0;
for (int i = 0; i < 26; ++i) {
if (cnt2[i] & 1) {
++suf;
}
}
int sum = suf;
ll ans = 0;
for (int i = 1; i < n; ++i) {
if (cnt1[s[i] - 'a'] & 1) {
--pre;
} else {
++pre;
}
++cnt1[s[i] - 'a'];
if (cnt2[s[i] - 'a'] & 1) {
--suf;
} else {
++suf;
}
--cnt2[s[i] - 'a'];
// A,B 非空,因此空至少一个位置给 B
if (i > 1) {
ll x = nxt[i + 1] / i + 1;
ans += (x - x / 2) * query(suf + 1) + (x / 2) * query(sum + 1);
}
update(pre + 1, 1);
// 因为下表可能为 0,所以树状数组下标整体加一
}
printf("%lld\n", ans);
}
int main() {
int T = 1;
scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}