P7114 [NOIP2020] 字符串匹配

Link:https://www.luogu.com.cn/problem/P7114

知识点:枚举,结论,Z 函数,哈希

唉,三年了,三年!!!

简述

T 组数据,每组数据给定仅由小写字母组成的字符串 s,求 t=(AB)iC 的方案数,其中 F(A)F(C),其中 F(t) 表示字符串 t 中出现奇数次的字符的数量。两种方案不同当且仅当拆分出的 ABC 中有至少一个字符串不同。
对于所有测试点,保证 1T51|s|220
1S,512MB。

分析

O(|s|ln|s|log26) 考场垃圾做法

看到古老的代码了回忆一下。

首先预处理所有前缀和后缀中出现次数为奇数次的字符的数量,然后考虑枚举 AB 的长度,再枚举 (AB)i 的长度,可以通过字符串哈希判断枚举的 (AB)i 是否合法。确定了 (AB)iC 唯一确定,仅需求所有 A 的可行值中满足 F(A)F(C) 的数量,可通过树状数组维护 F(A) 求得。

枚举 (AB)i 的时间复杂度是调和级数,总时间复杂度为 O(|s|ln|s|log26)

考场上写丑了清空居然全都是暴力 memset 并且树状数组值域开到了 N、、、其实把 memeset 换成枚举并且把树状数组值域改小可以概率卡过去。考场上写了什么几把啊我草幸亏没被卡多测清空好歹水到 84 分不然省一没了呜呜

O(nlog26) 真算法

同样考虑枚举 AB,但是接下来不暴力枚举 (AB)i 而是进一步深挖性质。

首先考虑以 AB 为循环节的最长前缀可以到什么位置,即求合法的 (AB)ii 的最大值。手玩了下发现可以通过求 s[|AB|+1:n]s 的最长公共前缀,也即 Z 函数求得,满足:

max{i}=min{z(|AB|+1)|AB|+1,|s|1|AB|}

注意 A,B,C 都要求非空,所以上式中有一个取 min 防止出现 C 非空的情况。

然后考虑为什么要强调 F 代表出现次数为奇数的字符数量?手玩下发现 (AB)2k 中所有字符出现次数均为偶数,也即当 i 的变化量为偶数时 F(C) 是不变的,仅需讨论 i 为奇数与偶数的情况即可确定 F(C),再求有多少 A 合法即可,不需要再枚举 (AB)i 了。

i 为奇数的个数为 max{i}+12,此时有 F(C)=F(s[|AB|+1:|s|])i 为偶数的个数为 max{i}2,此时有 F(C)=F(s[2×|AB|+1:|s|])。同样可通过树状数组维护 F(A) 求合法 A 的数量。

比考场做法少了枚举 (AB)i 的调和级数,总时间复杂度为 O(|s|log26) 级别。

已经有了不依赖于字符集大小纯线性的做法?牛逼,但是懒了。

代码

O(|s|log26) 真算法:

复制复制
//
/*
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|log26) 考场垃圾做法:

以下为考场代码。

//
/*
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;
}
posted @   Luckyblock  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示