洛谷P4462 [CQOI2018] 异或序列 题解
题意:
已知一个长度为 \(n\) 的整数数列 \(a_1,a_2,\dots,a_n\),给定查询参数 \(l,r\),问在 \(a_l,a_{l+1},\dots,a_r\) 区间内,有多少子区间满足异或和等于 \(k\)。也就是说,对于所有的 \(x,y (l \leq x \leq y \leq r)\),能够满足 \(a_x \bigoplus a_{x+1} \bigoplus \dots \bigoplus a_y = k\) 的 \(x,y\) 有多少组。
输入格式:
输入文件第一行,为 \(3\) 个整数 \(n,m,k\)。
第二行为空格分开的 \(n\) 个整数,即 \(a_1,a_2,..a_n\)。
接下来 \(m\) 行,每行两个整数 \(l_j,r_j\),表示一次查询。
输出格式:
输出文件共 \(m\) 行,对应每个查询的计算结果。
数据范围:
对于 \(30\%\) 的数据,\(1 \leq n, m \leq 1000\)。
对于 \(100\%\) 的数据,\(1 \leq n, m \leq 10^5\),\(0 \leq k, a_i \leq 10^5\),\(1 \leq l_j \leq r_j \leq n\)。
发现我连莫队的板子都没有,赶紧补一补。关键词:莫队 模板
区间异或和可以用前缀和预处理一下,题目就变成了 \([l - 1,r]\) 内存在多少个二元组 \((x,y)\) 满足 \(x\oplus y = k\) 。
然后我们来讲这道题怎么用莫队做,以及莫队是啥。
莫队是一种离线+分块的暴力算法,大概的感觉就是两个小手🖐️在询问间跳来跳去。
莫队的使用非常广泛甚至可以扯到组合计数题(反正我不会),比如今天集训的防AK题(反正听不懂)
例如一个询问 \([3,5]\) 和另一个询问 \([1,4]\) ,我们先暴力把 \([3,5]\) 的答案算出来。
那么怎么算 \([1,4]\) 呢?此时两个小手🖐️ \(l,r\) 分别在 \(3,5\) 上,我们把他们跳到 \(1,4\) 上。
具体地,\(l\) 从 \(3\) 暴力移到 \(1\) ,然后把答案算进去,此时两个小手🖐️ \(l,r\) 分别在 \(1,5\) 上,并且我们有 \([1,5]\) 的答案了
然后我们把 \(r\) 从 \(5\) 暴力移到 \(4\) ,并把原来计算的贡献删掉。最后两个小手🖐️ \(l,r\) 就分别在 \(1,4\) 啦!
那么问题来了,这跟暴力有啥区别?哎,都用分块了,这能叫暴力吗。
不妨记块长为 \(B\) ,我们按照以下规则对询问 \(q_i\) 进行排序:
- 询问 \(l\) 所处的块相同时,按 \(r\) 递增排序。
- 询问 \(l\) 所处的块不同时,按所处块的编号 \(\mathtt{bl}\) 递增排序。
然后我们来分析一下复杂度(认为 \(n,m\) 同阶)。
- 在同一个块内,右端点移动不超过 \(\mathcal{O}(n)\) ,有 \(\mathcal{O}(\frac{n}{B})\) 个块,这部分复杂度为 \(\mathcal{O}(\frac{n^2}{B})\)
- 在块间转移时,右端点移动不超过 \(\mathcal{O}(n)\) ,有 \(\mathcal{O}(\frac{n}{B})\) 个块,这部分复杂度为 \(\mathcal{O}(\frac{n^2}{B})\)
- 在同一个块内,左端点移动不超过 \(\mathcal{O}(B^2)\) ,有 \(\mathcal{O}(\frac{n}{B})\) 个块,这部分复杂度为 \(\mathcal{O}(nB)\)
- 在块间转移时,左端点移动不超过 \(\mathcal{O}(2B)\) ,有 \(\mathcal{O}(\frac{n}{B})\) 个块,这部分复杂度为 \(\mathcal{O}(n)\)
因此总复杂度为 \(\mathcal{O}(nB+\frac{n^2}{B})\) ,\(B\) 取 \(\sqrt{n}\) 时复杂度为 \(\mathcal{O}(n\sqrt{n})\) 最优。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(1e5 + 15))
int n,m,k,B,res,a[N],ans[N],o[N * 2]; // o[i]表示当前区间有多少个数异或和为i
struct node { int l,r,id,bl; } q[N];
bool cmp(node x, node y) {
return x.bl == y.bl ? x.r < y.r : x.bl < y.bl;
}
void add(int x) { res += o[x ^ k]; ++o[x]; }
void del(int x) { --o[x]; res -= o[x ^ k]; }
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n >> m >> k; B = sqrt(n);
for(int i = 1; i <= n; i++) { cin >> a[i]; a[i] ^= a[i - 1]; }
for(int i = 1; i <= m; i++) {
cin >> q[i].l >> q[i].r; --q[i].l; // 本题中是前缀和,因此要把 l 减去 1
q[i].id = i; q[i].bl = (q[i].l - 1) / B + 1;
}
sort(q + 1, q + 1 + m, cmp); int l = 1, r = 0;
for(int i = 1; i <= m; i++)
{
// 莫队的删除一定要在添加之后!!!否则在一些题目会出现计数为负数的情况!!
while(l > q[i].l) add(a[--l]);
while(r < q[i].r) add(a[++r]);
while(l < q[i].l) del(a[l++]);
while(r > q[i].r) del(a[r--]);
ans[q[i].id] = res;
}
for(int i = 1; i <= m; i++) cout << ans[i] << '\n';
return 0;
}