洛谷 P7114 / LOJ 3387 「NOIP2020」字符串匹配

洛谷传送门

LOJ 传送门

思路

|S|=n。首先求出 SZ 函数数组 nxt,考虑将 AB 视作一个整体的循环节,那么长度为 i 的循环节最多可以接上 nxti+1i+1 组。

这个理解起来很简单。根据定义,子串 [i+1,i+nxti+1][1,nxti+1] 相同,即 [1,i][i+1,2i] 相同,[i+1,2i][2i+1,3i],相同,以此类推。

然后考虑题中 F(A)F(C) 的限制。枚举 AB 的长度,记 f(i,j)[i,j] 中出现次数为奇数的字符数量。令循环节出现次数为 t,发现只用考虑 t 为奇数或偶数的情况,因为 C(AB)xC(AB)x+2 的字符出现次数的奇偶情况相同。设 |A|=j (j<i),当 t 为奇数,则 j 可行当且仅当 f(1,j)f(i+1,n);当 t 为偶数,则 j 可行当且仅当 f(1,j)f(2i+1,n)f(1,j)f(1,n)。用树状数组维护当前所有前缀 [1,k] (k<i) 中出现次数为奇数的字符数量为 x 的前缀个数。易知 t 为奇数时有 tt2 种情况,为偶数时有 t2 种情况,乘法原理计算即可。

时间复杂度 O(Tnlog26)。实现细节见代码。

代码

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;
}
posted @   zltzlt  阅读(70)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示