Codeforces Gym 103409H. Popcount Words
Codeforces Gym 103409H. Popcount Words
首先得先观察 是个什么玩意儿.
通过枚举可发现, 其满足:
记 . 于是上面的规律可以表示为:
记 为数 在二进制下 的个数的奇偶性, 则有
根据以上几条性质, 便可以得出一个结论:
结论 对于每一个 可以由 个 拼接而成.
构造 构建一个长度为 的 0-base线段树, 然后将该区间 放置在线段树上, 会有 个区间 , 且这些区间都满足 . 故该 个区间即为所求.
于是我们将原题 个区间 按照上述方式分解, 可得到共 个 .
回到原题. 多串匹配可以尝试建立询问串 的 AC自动机. 如果能求出串 经过每个节点的次数, 那么询问串 的答案就是对应节点 子树的次数和.
记 表示AC自动机上的节点 经过串 后到达的节点. 那么根据 的转移可知, 有状态转移方程 . 这里是一个朴素的 .
但我们要求的是每个节点被经过了多少次, 而并不是最后会到达哪个节点. 于是可以再记 为节点 经过串 上沿路的 个节点 ( 不包括 ) 一起出现的次数. 那么最后原串 经过节点 的次数为 , 其中节点 分别满足 .
记 为节点 经过串 的次数. 按照从大到小的顺序转移 , 则可得出其状态转移方程为 . 在求出 之后, 便可求出 经过每个节点的次数, 最后在 树上统计子树之和就可以得到答案.
计算 和 的时空复杂度均为 . 预处理 的时间复杂度为 . 于是总时间复杂度为 .
参考代码
#include <bits/stdc++.h>
using namespace std;
static constexpr int Maxn = 1e5 + 5, Maxm = 5e5 + 5, MaxC = 2, Maxq = 1e5 + 5;
static constexpr int LOG = 30;
int n, q;
int L[Maxn], R[Maxn];
int endpos[Maxq];
int64_t S[Maxm];
int m, N[LOG * 2], I[LOG * 2];
void get_build(int ql, int qr, int l, int r) {
if (l >= qr || ql >= r) return ;
if (ql <= l && r <= qr) {
int k = 31 - __builtin_clz(r - l);
int q = l >> k;
N[m] = k, I[m] = __builtin_parity(q); ++m;
return ;
}
int mid = (l + r) >> 1;
get_build(ql, qr, l, mid);
get_build(ql, qr, mid, r);
} // get_build
int ch[Maxm][MaxC], fail[Maxm];
int root, tot;
static int que[Maxm];
int insert(int len, const char *s) {
int u = root;
for (int i = 0; i < len; ++i) {
int c = s[i] - '0';
if (!ch[u][c])
ch[u][c] = ++tot;
u = ch[u][c];
}
return u;
} // insert
void build(void) {
int qh = 0, qe = 0;
fail[root] = root;
for (int j = 0; j < 2; ++j) {
if (ch[root][j]) {
fail[ch[root][j]] = root;
que[qe++] = ch[root][j];
} else {
ch[root][j] = root;
}
}
while (qh < qe) {
int u = que[qh++];
for (int j = 0; j < 2; ++j) {
if (ch[u][j]) {
fail[ch[u][j]] = ch[fail[u]][j];
que[qe++] = ch[u][j];
} else {
ch[u][j] = ch[fail[u]][j];
}
}
}
} // build
int f[2][LOG][Maxm], c[2][LOG][Maxm];
int64_t g[2][LOG + 1][Maxm];
int main(void) {
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; ++i)
scanf("%d%d", &L[i], &R[i]);
root = 0; tot = 0;
for (int i = 1; i <= q; ++i) {
static char str[Maxm];
scanf("%s", str);
endpos[i] = insert(strlen(str), str);
}
build();
for (int i = 0; i <= tot; ++i)
f[0][0][i] = ch[i][0], f[1][0][i] = ch[i][1];
for (int j = 1; j < LOG; ++j)
for (int i = 0; i <= tot; ++i) {
f[0][j][i] = f[1][j - 1][f[0][j - 1][i]];
f[1][j][i] = f[0][j - 1][f[1][j - 1][i]];
}
int cur = 0;
for (int i = 1; i <= n; ++i) {
m = 0, get_build(L[i], R[i] + 1, 0, 1 << 30);
for (int j = 0; j < m; ++j) {
c[I[j]][N[j]][cur]++;
cur = f[I[j]][N[j]][cur];
}
}
for (int j = LOG - 1; j >= 0; --j)
for (int i = 0; i <= tot; ++i) {
g[0][j][i] += c[0][j][i] + g[0][j + 1][i];
g[1][j][i] += c[1][j][i] + g[1][j + 1][i];
g[1][j][f[0][j][i]] += g[0][j + 1][i];
g[0][j][f[1][j][i]] += g[1][j + 1][i];
}
memset(S, 0, sizeof(S));
for (int i = 0; i <= tot; ++i) {
S[ch[i][0]] += g[0][0][i];
S[ch[i][1]] += g[1][0][i];
}
for (int i = tot; i >= 0; --i)
S[fail[que[i]]] += S[que[i]];
for (int i = 1; i <= q; ++i)
printf("%lld\n", S[endpos[i]]);
exit(EXIT_SUCCESS);
} // main
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】