1 条题解
-
0
一、题目重述
给定长度为 的数组 ,常数 (),以及 个操作:
- 单点修改:
- 区间查询:求 内有多少个子数组 满足子数组的按位或结果
需要在线回答所有查询。
二、核心难点
- 直接枚举子数组 不可行
- 按位或运算不可逆,不能直接用前缀和或差分
- 需要支持单点修改,不能离线
三、关键性质
性质 1:对于固定左端点 ,当 向右移动时, 的值单调不降(二进制位只会从 0 变 1,不会变回 0)。
性质 2:由于 ,每个数的二进制位最多 位。
因此,对于任意起始位置, 值的变化次数最多 次(每次至少新增加一个 1 位)。性质 3:这个性质可以推广到任意区间:
一个区间内,所有前缀 OR 的不同值个数 ≤ 20,所有后缀 OR 的不同值个数 ≤ 20。
四、算法思路:线段树 + 分治
我们使用线段树维护每个区间 的信息:
left:该区间所有前缀 OR 值及其出现次数(从 开始向右的 OR 值变化段)right:该区间所有后缀 OR 值及其出现次数(从 开始向左的 OR 值变化段)cnt:该区间内所有子数组中 OR 值 的个数
4.1 叶子节点(单个元素 )
left = {(v, 1)}right = {(v, 1)}cnt = 1如果 ,否则
4.2 合并两个子区间
设左子区间为 ,右子区间为 ,合并后区间为 。
1. 跨越中点的子数组统计
跨越中点的子数组 = 左区间的某个后缀 + 右区间的某个前缀
其 OR 值 = 后缀 OR 前缀 OR我们枚举左后缀的每个 OR 值 (出现次数 ),以及右前缀的每个 OR 值 (出现次数 ),若 ,则贡献 。
直接枚举是 ,,可接受。
优化:固定 ,右前缀的 OR 值 单调不降(因为前缀越长 OR 越大)。
我们可以对右前缀预处理后缀和,然后用双指针快速统计,复杂度 。2. 合并 left 信息
的前缀 OR = 的前缀 OR,再拼接上 的前缀 OR(但需要整体 OR 上 整个区间的 OR 值)。
更具体地:
设 的 left 列表为
的 left 列表为则 的 left 列表 = ,然后依次将 的每个 与 的总 OR 值做 OR 后合并到末尾(若相同则合并计数,否则新增)。
3. 合并 right 信息
对称处理, 的后缀 OR = 的后缀 OR,再拼接上 的后缀 OR(整体 OR 上 的总 OR 值)。
4. 合并 cnt
五、数据结构实现
由于 很小,我们可以用固定大小的数组存储
left和right,避免动态内存分配带来的常数开销。每个
left/right最多 个元素,因此用array<pair<int,int>, B+2>存储,并用left_sz记录实际长度。这样
merge操作中完全无动态分配,常数极小。
六、复杂度分析
- 建树:每个节点合并 ,共 个节点,总
- 单点修改:沿路径更新 个节点,每个节点合并 ,总
- 区间查询:将查询区间分解为 个节点,依次合并,总
,,
总操作次数 ≈ $400 \times 17 \times (n + m) \approx 6.8 \times 10^6 \times 17$?实际更小,因为不是所有节点都合并 。实测能通过 的极限数据。
七、最终代码(标程)
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; const int B = 20; int n, m, x; int a[N]; struct Node { array<pair<int, int>, B + 2> left, right; int left_sz, right_sz; long long cnt; } tr[N << 2]; Node merge(const Node& L, const Node& R) { Node res; res.cnt = L.cnt + R.cnt; const auto& lvals = L.right; const auto& rvals = R.left; int lsz = L.right_sz, rsz = R.left_sz; // 后缀和优化 long long suffix[B + 2] = {0}; for (int i = rsz - 1; i >= 0; --i) { suffix[i] = suffix[i + 1] + rvals[i].second; } int j = 0; for (int i = 0; i < lsz; ++i) { int or_l = lvals[i].first; int cnt_l = lvals[i].second; while (j < rsz && (or_l | rvals[j].first) < x) ++j; if (j < rsz) { res.cnt += 1LL * cnt_l * suffix[j]; } } // 合并 left res.left_sz = 0; for (int i = 0; i < L.left_sz; ++i) { res.left[res.left_sz++] = L.left[i]; } int last = res.left[res.left_sz - 1].first; for (int i = 0; i < R.left_sz; ++i) { int cur = last | R.left[i].first; if (cur == res.left[res.left_sz - 1].first) { res.left[res.left_sz - 1].second += R.left[i].second; } else { res.left[res.left_sz++] = {cur, R.left[i].second}; } last = cur; } // 合并 right res.right_sz = 0; for (int i = 0; i < R.right_sz; ++i) { res.right[res.right_sz++] = R.right[i]; } last = res.right[res.right_sz - 1].first; for (int i = 0; i < L.right_sz; ++i) { int cur = last | L.right[i].first; if (cur == res.right[res.right_sz - 1].first) { res.right[res.right_sz - 1].second += L.right[i].second; } else { res.right[res.right_sz++] = {cur, L.right[i].second}; } last = cur; } return res; } void build(int u, int l, int r) { if (l == r) { tr[u].left[0] = {a[l], 1}; tr[u].right[0] = {a[l], 1}; tr[u].left_sz = tr[u].right_sz = 1; tr[u].cnt = (a[l] >= x ? 1 : 0); return; } int mid = (l + r) >> 1; build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r); tr[u] = merge(tr[u << 1], tr[u << 1 | 1]); } void update(int u, int l, int r, int pos, int val) { if (l == r) { a[pos] = val; tr[u].left[0] = {val, 1}; tr[u].right[0] = {val, 1}; tr[u].left_sz = tr[u].right_sz = 1; tr[u].cnt = (val >= x ? 1 : 0); return; } int mid = (l + r) >> 1; if (pos <= mid) update(u << 1, l, mid, pos, val); else update(u << 1 | 1, mid + 1, r, pos, val); tr[u] = merge(tr[u << 1], tr[u << 1 | 1]); } Node query(int u, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) return tr[u]; int mid = (l + r) >> 1; if (qr <= mid) return query(u << 1, l, mid, ql, qr); if (ql > mid) return query(u << 1 | 1, mid + 1, r, ql, qr); Node left = query(u << 1, l, mid, ql, qr); Node right = query(u << 1 | 1, mid + 1, r, ql, qr); return merge(left, right); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n >> m >> x; for (int i = 1; i <= n; ++i) cin >> a[i]; build(1, 1, n); while (m--) { int op; cin >> op; if (op == 1) { int i, y; cin >> i >> y; update(1, 1, n, i, y); } else { int l, r; cin >> l >> r; Node res = query(1, 1, n, l, r); cout << res.cnt << '\n'; } } return 0; }
- 1
信息
- ID
- 7243
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 7
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者