莫队二次离线
莫队二次离线
前置芝士#
算法介绍#
第一步优化#
对于普通的莫队,我们求没有修改的若干个询问,将其离线,对询问分块,经过巧妙的排序,然后对于一个区间 \([l,r]\) 通过计算得到 \([l, r+1], [l, r - 1], [l - 1, r], [l + 1, r]\) 的答案。
设计算单点造成的答案差值的时间复杂度为 \(O(k)\)。
那么总的时间复杂度是 \(O(nk\sqrt n)\)。
对于某些毒瘤题 由于 \(k\) 太大,这是远远不够的。
那么怎么办呢?
反正都离线了,就乱搞呗。
考虑到某些问题单点差值可以表示为一段区间的贡献减去另一端区间的贡献时,
我们可以将其转化为前缀和的形式,进行预处理。
那么此时的差值计算是 \(O(1)\) 的,
总的时间复杂度也就降到了 \(O(nk + n \sqrt n)\)。
再次优化#
十分的优秀了!可惜还不够。
我们会发现空间是 \(O(n \sqrt n)\) 的,而且常数较大。
那么如何去进一步优化呢?
举个栗子:
你现在的区间是 \([10, 20]\) 你要拓展到 \([10, 25]\)
那么就会被拆为
询问 \([1, 21]\) 对应的 \(r = 21\) 时的贡献
询问 \([1, 22]\) 对应的 \(r = 22\) 时的贡献
\(\cdots\)
询问 \([1, 25]\) 对应的 \(r = 25\) 时的贡献
以上的全都是权值 +1
然后是
询问 \([1, 9]\) 对应的 \(r = 21\) 时的贡献
询问 \([1, 9]\) 对应的 \(r = 22\) 时的贡献
\(\cdots\)
询问 \([1, 9]\) 对应的 \(r = 25\) 时的贡献
以上的全都是权值 -1
那么我们就分为两个部分 +1
和 -1
下面面对 LuoguP4887 具体举例
Case1: 求+1部分的和#
那么我们可以跑一边扫描线,对于每一个位置 \(i\) 求出 \([1,i-1]\) 这个前缀有多少个数字和 \(a_{i}\) 异或起来有 \(k\) 个1。
具体点来讲我们实现一个 \(O(k)\) 插入 \(O(1)\) 询问的数组 \(sum\)。
假设我们跑扫描线的时候跑到了第 \(i\) 个点那么 \(sum_j\) 就表示 \([1,i]\) 这个区间中有多少个数字和 \(a_{j}\) 异或起来有 \(k\) 个1。
那么我们插入一个 \(a\) 值的时候就直接大力的枚举所有有 \(k\) 个1的数字 \(x\) 然后令 \(sum_{a \oplus x}\) 加一。
接下来我们维护一个变量 \(K\) 表示“对与 \([1, i]\) 中的每一个位置 \(i\) ,满足 \([1, i - 1]\) 这个前缀 \(a_{i}\) 异或起来有k个1的数字的数目的和”。
那么对于上面那个栗子,我们只需要用 \(r = 23\) 的 \(K\) 减去 \(r = 20\) 的 \(K\) 即可。
Case2: 求-1部分的和#
我们发现这一部分是对于一个固定前缀询问了一个区间里有多少个数异或起来刚好有 \(k\) 个1。
那么我们可以在前缀右端点开一个 vector
然后存入后面询问的区间,注意这边还要存入一个符号和询问的编号。
然后我们就可以在跑扫描线的时候挨个暴力询问。
我们就如此神奇的把空间压缩成了 \(O(m)\)
例题实现#
#include <bits/stdc++.h> using ll = int64_t; static constexpr int N = 1e5 + 5; int n, m, k, a[N], b[N], bel[N], sum[N]; ll ans[N]; class Query { public: int l, r, id; public: ll ans; public: bool operator < (const Query &a) {return bel[l] == bel[a.l] ? r < a.r : l < a.l;} } q[N]; std::vector<std::tuple<int, int, int>> v[N]; auto main() -> decltype(0) { scanf("%d%d%d", &n, &m, &k); if (k > 14) {for (int i = 1; i <= m; ++i) puts("0"); return 0;} for (int i = 1; i <= n; ++i) scanf("%d", a + i); for (int i = 1; i <= m; ++i) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i; std::vector<int> bucket; for (int i = 0; i < (1 << 14); ++i) if (__builtin_popcount(i) == k) bucket.emplace_back(i); int size = std::sqrt(n); for (int i = 1; i <= n; ++i) bel[i] = (i - 1) / size + 1; std::sort(q + 1, q + m + 1); for (int i = 1; i <= n; ++i) { for (int x : bucket) ++ b[a[i] ^ x]; sum[i] = b[a[i + 1]]; } std::memset(b, 0, sizeof b); for (int i = 1, l = 1, r = 0; i <= m; ++i) { int ql = q[i].l, qr = q[i].r; if (l < ql) v[r].emplace_back(l, ql - 1, -i); if (l > ql) v[r].emplace_back(ql, l - 1, i); while (l < ql) q[i].ans += sum[l - 1], ++ l; while (l > ql) q[i].ans -= sum[l - 2], -- l; if (r < qr) v[l - 1].emplace_back(r + 1, qr, -i); if (r > qr) v[l - 1].emplace_back(qr + 1, r, i); while (r < qr) q[i].ans += sum[r], ++ r; while (r > qr) q[i].ans -= sum[r - 1], -- r; } for (int i = 1, l, r, id; i <= n; ++i) { for (int j : bucket) ++ b[a[i] ^ j]; for (auto x : v[i]) { std::tie(l, r, id) = x; for (int j = l, tmp = 0; j <= r; ++j) { tmp = b[a[j]]; if (j <= i && !k) -- tmp; q[std::abs(id)].ans += tmp * (id < 0 ? -1 : 1); } } } for (int i = 1; i <= m; ++i) q[i].ans += q[i - 1].ans; for (int i = 1; i <= m; ++i) ans[q[i].id] = q[i].ans; for (int i = 1; i <= m; ++i) printf("%lld\n", ans[i]); return 0; }
作者: xxcxu
出处:https://www.cnblogs.com/Maraschino/p/16657034.html
本站使用「CC BY 4.0」创作共享协议,转载请在文章明显位置注明作者及出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)