题目描述
难度分:2207
输入n(1≤n≤2×105)和两个长为n的数组a,b,元素范围在[0,228)。
从a中选一个数a[i],从b中选一个数b[j],相加得到a[i]+b[j],这一共有n2个数字。
输出这n2个数的异或和。
输入样例1
2
1 2
3 4
输出样例1
2
输入样例2
6
4 6 0 0 3 3
0 5 6 5 0 3
输出样例2
8
输入样例3
5
1 2 3 4 5
1 2 3 4 5
输出样例3
2
输入样例4
1
0
0
输出样例4
0
算法
拆位+二分
一般这样的位运算题目都要逐位考虑。
若ai+bj的第k位是1,则ai+bj有可能是以下两种情况:
- 从
1000...000
到1111...111
(每个二进制数是[0,k]位),转换成十进制就是[2k,2k+1−1]。 - 如果进位了的话就是从
1100...000
到1111...111
(每个二进制数是[0,k+1]位),转换成十进制就是[2k+1+2k,2k+2−1]=[3×2k,2k+2−1]。
此时如果枚举ai,问题就转化为有多少个b[j]在[2k−ai,2k+1−1−ai]或[3×2k−ai,2k+2−1−ai]范围内,把b排序后,二分查找一下就可以确定出来。
根据异或的性质,只要这个数量是奇数,就能保证异或操作之后第k位是1。
复杂度分析
时间复杂度
遍历总的位数是29位,设其为A。对于每一位,首先需要对b进行O(nlog2n)的排序,之后才能在遍历ai的过程中,通过二分查找确定符合题意的bj。而遍历ai的时间复杂度是O(n),二分查找的时间复杂度是O(log2n),因此时间复杂度同样是O(nlog2n)。因此,算法整体的时间复杂度是O(Anlog2n)。
空间复杂度
在考虑每一位k的结果时,会先将比k高的位清空,避免高位的影响。因此,需要对a和b进行拷贝,额外空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 200010;
int n, a[N], b[N], c[N], d[N];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; i++) {
scanf("%d", &b[i]);
}
int ans = 0;
for(int k = 0; k <= 28; k++) {
// 把第k位以上的截断
for(int i = 1; i <= n; i++) {
c[i] = a[i]&((1<<(k + 1)) - 1);
d[i] = b[i]&((1<<(k + 1)) - 1);
}
sort(d + 1, d + n + 1);
LL cnt = 0;
for(int i = 1; i <= n; i++) {
int l = (1<<k) - c[i], r = (1<<(k + 1)) - 1 - c[i];
cnt += upper_bound(d + 1, d + 1 + n, r) - lower_bound(d + 1, d + 1 + n, l);
l = 3*(1<<k) - c[i], r = (1<<(k + 2)) - 1 - c[i];
cnt += upper_bound(d + 1, d + 1 + n, r) - lower_bound(d + 1, d + 1 + n, l);
}
if(cnt&1) ans |= 1<<k;
}
printf("%d\n", ans);
return 0;
}