Link:https://www.luogu.com.cn/problem/P7114
知识点:枚举,结论,Z 函数,哈希
唉,三年了,三年!!!
简述
\(T\) 组数据,每组数据给定仅由小写字母组成的字符串 \(s\),求 \(t = {(AB)}^iC\) 的方案数,其中 \(F(A) \le F(C)\),其中 \(F(t)\) 表示字符串 \(t\) 中出现奇数次的字符的数量。两种方案不同当且仅当拆分出的 \(A\)、\(B\)、\(C\) 中有至少一个字符串不同。
对于所有测试点,保证 \(1 \le T \le 5\),\(1 \le |s| \le 2^{20}\)。
1S,512MB。
分析
\(O(|s|\ln |s|\log 26)\) 考场垃圾做法
看到古老的代码了回忆一下。
首先预处理所有前缀和后缀中出现次数为奇数次的字符的数量,然后考虑枚举 \(AB\) 的长度,再枚举 \((AB)^i\) 的长度,可以通过字符串哈希判断枚举的 \((AB)^i\) 是否合法。确定了 \((AB)^i\) 后 \(C\) 唯一确定,仅需求所有 \(A\) 的可行值中满足 \(F(A)\le F(C)\) 的数量,可通过树状数组维护 \(F(A)\) 求得。
枚举 \((AB)^i\) 的时间复杂度是调和级数,总时间复杂度为 \(O(|s|\ln |s|\log 26)\)。
考场上写丑了清空居然全都是暴力 memset 并且树状数组值域开到了 \(N\)、、、其实把 memeset 换成枚举并且把树状数组值域改小可以概率卡过去。考场上写了什么几把啊我草幸亏没被卡多测清空好歹水到 84 分不然省一没了呜呜
\(O(n\log 26)\) 真算法
同样考虑枚举 \(AB\),但是接下来不暴力枚举 \((AB)^i\) 而是进一步深挖性质。
首先考虑以 \(AB\) 为循环节的最长前缀可以到什么位置,即求合法的 \((AB)^i\) 的 \(i\) 的最大值。手玩了下发现可以通过求 \(s[|AB| + 1: n]\) 与 \(s\) 的最长公共前缀,也即 Z 函数求得,满足:
\[\max\{i\} = \min\left\{ \left\lfloor \dfrac{z(|AB| + 1)}{|AB|} \right\rfloor + 1, \dfrac{|s| - 1}{|AB|} \right\} \]注意 \(A, B, C\) 都要求非空,所以上式中有一个取 \(\min\) 防止出现 \(C\) 非空的情况。
然后考虑为什么要强调 \(F\) 代表出现次数为奇数的字符数量?手玩下发现 \((AB)^{2k}\) 中所有字符出现次数均为偶数,也即当 \(i\) 的变化量为偶数时 \(F(C)\) 是不变的,仅需讨论 \(i\) 为奇数与偶数的情况即可确定 \(F(C)\),再求有多少 \(A\) 合法即可,不需要再枚举 \((AB)^{i}\) 了。
\(i\) 为奇数的个数为 \(\left\lfloor\frac{\max\{i\} + 1}{2}\right\rfloor\),此时有 \(F(C) = F(s[|AB| + 1: |s|])\);\(i\) 为偶数的个数为 \(\left\lfloor\frac{\max\{i\}}{2}\right\rfloor\),此时有 \(F(C) = F(s[2\times |AB| + 1: |s|])\)。同样可通过树状数组维护 \(F(A)\) 求合法 \(A\) 的数量。
比考场做法少了枚举 \((AB)^i\) 的调和级数,总时间复杂度为 \(O(|s|\log 26)\) 级别。
已经有了不依赖于字符集大小纯线性的做法?牛逼,但是懒了。
代码
\(O(|s|\log 26)\) 真算法:
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 5e5 + 10;
const int kM = 30;
//=============================================================
int n, z[kN];
int cnt_pre[30], cnt_suf[30], sum_pre[kN], sum_suf[kN];
char s[kN];
LL ans;
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
namespace Bit {
#define lowbit(x) ((x)&-(x))
int time, t[kM], tim[kM];
void Init() {
++ time;
}
void Insert(int pos_) {
++ pos_;
for (int i = pos_; i <= 27; i += lowbit(i)) {
if (tim[i] != time) t[i] = 0, tim[i] = time;
t[i] ++;
}
}
int Sum(int pos_) {
++ pos_;
int ret = 0;
for (int i = pos_; i; i -= lowbit(i)) {
if (tim[i] != time) t[i] = 0, tim[i] = time;
ret += t[i];
}
return ret;
}
#undef lowbit
}
void z_function() {
z[1] = n;
for (int i = 2, l = 1, r = 1; i <= n; ++ i) {
if (i <= r && z[i - l + 1] < r - i + 1) {
z[i] = z[i - l + 1];
} else {
z[i] = std::max(0, r - i + 1);
while (i + z[i] <= n && s[z[i] + 1] == s[i + z[i]]) ++ z[i];
}
if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
}
}
void Init() {
ans = 0;
scanf("%s", s + 1); n = strlen(s + 1);
for (int i = 0; i <= 27; ++ i) cnt_pre[i] = cnt_suf[i] = 0;
for (int i = 1; i <= n + 1; ++ i) sum_pre[i] = sum_suf[i] = 0;
for (int i = 1; i <= n; ++ i) {
++ cnt_pre[s[i] - 'a'];
if (cnt_pre[s[i] - 'a'] % 2 == 1) sum_pre[i] = sum_pre[i - 1] + 1;
else sum_pre[i] = sum_pre[i - 1] - 1;
}
for (int i = n; i >= 1; -- i) {
++ cnt_suf[s[i] - 'a'];
if (cnt_suf[s[i] - 'a'] % 2 == 1) sum_suf[i] = sum_suf[i + 1] + 1;
else sum_suf[i] = sum_suf[i + 1] - 1;
}
z_function();
Bit::Init();
}
void Solve() {
for (int ab = 2; ab <= n; ++ ab) {
int cnt = std::min(z[ab + 1] / ab + 1, (n - 1) / ab);
Bit::Insert(sum_pre[ab - 1]);
ans += 1ll * (cnt + 1) / 2ll * Bit::Sum(sum_suf[ab + 1]);
ans += 1ll * cnt / 2ll * Bit::Sum(sum_suf[1]);
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
int T = read();
while (T --) {
Init();
Solve();
printf("%lld\n", ans);
}
return 0;
}
/*
1
aaaaaa
1
aaaa
*/
\(O(|s|\ln |s|\log 26)\) 考场垃圾做法:
以下为考场代码。
//
/*
By:Luckyblock
fuck
ccf
//freopen
我知道会有人看见的。
这里是一个已退役选手,在结束前 10 分钟的随想。
近 2 年的喜怒哀乐,在脑中缓缓流淌。
我曾妄想:挥斥方遒,信步走出考场——
终不如愿,
别了,
我愿为你鼓掌。
Goodbye,World!
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e6 + 4e5 + 10;
const int mod1 = 998244353;
const int mod2 = 1e9 + 7;
const int kBase = 23333;
//=============================================================
int n, cnt_pre[30], cnt_suf[30], sum_pre[kN], sum_suf[kN];
int pw1[kN], pw2[kN], has1[kN], has2[kN];
LL ans;
char s[kN];
//=============================================================
int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) {
if (ch == '-') f = -1;
}
for (; isdigit(ch); ch = getchar()) {
w = 10 * w + ch - '0';
}
return f * w;
}
namespace Bit {
#define low(x) (x&-x)
int t[kN];
void Init() {
memset(t, 0, sizeof (t));
}
void Insert(int pos_) {
for (int i = pos_; i <= n; i += low(i)) {
t[i] ++;
}
}
int Sum(int pos_) {
int ret = 0;
for (int i = pos_; i; i -= low(i)) {
ret += t[i];
}
return ret;
}
#undef low
}
void Init() {
ans = 0;
Bit::Init();
memset(cnt_pre, 0, sizeof (cnt_pre));
memset(cnt_suf, 0, sizeof (cnt_suf));
}
bool Judge(int r1_, int l2_, int r2_) {
int h1 = (1ll * (1ll * has1[r2_] - 1ll * has1[l2_ - 1] * pw1[r2_ - l2_ + 1] % mod1) % mod1 + mod1) % mod1;
int h2 = (1ll * (1ll * has2[r2_] - 1ll * has2[l2_ - 1] * pw2[r2_ - l2_ + 1] % mod2) % mod2 + mod2) % mod2;
return ((h1 == has1[r1_]) && (h2 == has2[r1_]));
}
void Prepare() {
Init();
scanf("%s", s + 1);
n = strlen(s + 1);
for (int i = 1; i <= n; ++ i) {
int now = s[i] - 'a';
cnt_pre[now] ++;
if (cnt_pre[now] % 2 == 1) {
sum_pre[i] = sum_pre[i - 1] + 1;
} else {
sum_pre[i] = sum_pre[i - 1] - 1;
}
has1[i] = 1ll * (1ll * has1[i - 1] * kBase % mod1 + now) % mod1;
has2[i] = 1ll * (1ll * has2[i - 1] * kBase % mod2 + now) % mod2;
}
for (int i = n; i; -- i) {
int now = s[i] - 'a';
cnt_suf[now] ++;
if (cnt_suf[now] % 2 == 1) {
sum_suf[i] = sum_suf[i + 1] + 1;
} else {
sum_suf[i] = sum_suf[i + 1] - 1;
}
}
}
//=============================================================
int main() {
int T = read();
pw1[0] = pw2[0] = 1;
for (int i = 1; i <= kN - 10; ++ i) {
pw1[i] = 1ll * pw1[i - 1] * kBase % mod1;
pw2[i] = 1ll * pw2[i - 1] * kBase % mod2;
}
while (T --) {
Prepare();
for (int ab = 2; ab < n; ++ ab) {
Bit::Insert(sum_pre[ab - 1] + 1);
ans += 1ll * Bit::Sum(sum_suf[ab + 1] + 1);
for (int c = 2 * ab; c + 1 <= n; c += ab) {
if (! Judge(ab, c - ab + 1, c)) break;
ans += 1ll * Bit::Sum(sum_suf[c + 1] + 1);
}
}
printf("%lld\n", ans);
}
return 0;
}
标签:suf,AB,P7114,int,kN,枚举,ch,字符串,NOIP2020
From: https://www.cnblogs.com/luckyblock/p/17977540