嘘~ 正在从服务器偷取页面 . . .

洛谷P4462 [CQOI2018] 异或序列 题解


洛谷P4462 [CQOI2018] 异或序列 题解

题目链接: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;
}

文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录