异或 xor

题目大意:有一个 n 个元素的数列 a,要进行 m 次查询,每次查询形式如下: 1. 给出两个整数 l,r ,表示查询区间的左右端点 2. 取出区间 [l,r] 中的所有出现了偶数次的整数。比如 1,2,1,2,1,则会 取出一个数 2 3. 将取出来的数全部异或起来,并将该异或值作为本次查询的答案 由于 David 计算能力很差,于是他请你来帮他计算一下每次询问的答案

数据结构好题

我们发现如果维护一个区间出现奇数次的数字的异或和就好办了,因为偶数次的都自己暴毙了(k^k=0)

所以我们希望对问题进行转化:每个区间中每个数字去掉出现的第一个之后,剩下的数字的异或和

用线段树或者树状数组来维护

在l的位置插入r以及他的问题id

然后一遍扫过去,统计答案,不断更新

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <bits/stdc++.h>
#define lowbit(x) (x & -x)
#define int long long
#define ll long long
using namespace std;

typedef pair<int, int> pr;

const int N = 1e6 + 66;

vector<pr>q[N];
map<int, int>vis, pre;
int n, m, now;
int a[N], b[N];
int nex[N], ans[N], tree[N];

inline void updata(int x, int val)
{
while (x <= n)
{
tree[x] ^= val;
x += lowbit(x);
}
return;
}

inline int query(int x)
{
int res(0);
while (x)
{
res ^= tree[x];
x -= lowbit(x);
}
return res;
}

signed main()
{
int i, j, l, r;
n = read();
for (i = 1; i <= n; ++ i)
{
a[i] = read();
++ vis[a[i]];
nex[pre[a[i]]] = i;
pre[a[i]] = i;
if (vis[a[i]] > 1) now ^= a[i];
b[i] = now;
}
m = read();
for (i = 1; i <= m; ++ i)
{
l = read(), r = read();
q[l].push_back(pr(r, i));
}
for (i = 1; i <= n; ++ i)
{
for (j = 0; j < (int)q[i].size(); ++ j) ans[q[i][j].second] = query(q[i][j].first) ^ b[q[i][j].first];
l = nex[i];
if (l) updata(l, a[i]);
}
for (i = 1; i <= m; ++ i) put(ans[i]);
return 0;
}

后记:

考虑到代码的实现,第一次写博客的时候,并未深刻搞懂代码实现,于11.25更新.

说明几个东西的定义:

b数组表示不计算每一个a[i]第一次出现的情况的异或和

tree数组表示的我们不断修改的一些东西.

来自zlc大佬的讲解:

1
2
3
4
5
6
7
8
9
10
11
就是询问离线,按照左端点排序,然后每次移动当前的左端点,修改整体的状态,并同时查询答案;

假如说现在枚举到了第i个点,那么第i个点在这个状态下就是 a[i]这个数出现的第一个位置,然后当我们把左端点移动到i+1时,a[i]的第一次出现的位置就会改变,假设变为nex[i].

然后就每次移动 左端点的时候吧 nex[i]的贡献消掉

直接消不好搞,因为原b数组是一个前缀异或,所以不在原来的数组上修改,多开一个树状数组开存修改的值

树状数组可以快速查询前缀

因为 ^ 这个运算可以消除(他的逆运算也是他自己) 所以可以按照代码那样的实现