题目描述
难度分:1423
输入n(1≤n≤105)、k(0≤k≤1012)和长为n的数组a(0≤a[i]≤1012)。
选择一个[0,k]中的整数x,最大化(x⊕a[1])+(x⊕a[2])+…+(x⊕a[n])。
输出这个最大值。
输入样例1
3 7
1 6 3
输出样例1
14
输入样例2
4 9
7 4 0 3
输出样例2
46
输入样例3
1 0
1000000000000
输出样例3
1000000000000
算法
按位贪心
这样的题目一般都要拆位,从k的最高位往低位考虑,在这个过程中构造x。在构造的过程中需要考虑x≤k这个限制,和数位DP
的过程类似,初始化一个islimit=true
。对于当前的位,分别考虑x在这一位是1还是0(如果islimit=true
,那这一位只能和k保持一致,否则这一位就能取0和1两个值),分别算出目标函数在这一位的答案t1和t2,然后分情况讨论x在当前位填的数:
- 如果当前位的填数上界ub=1。(1) 在t1>t2时这一位就可以填1。(2) 在t1≤t2时这一位就填0。如果当前填的数和k原本的不一致,那么islimit=
false
,否则islimit保持不变。 - 如果当前的填数上界ub=0,那就无所谓了,当前位只能填0,无论如何也不会改变islimit的取值。
得到x之后,遍历数组把目标函数算出来就可以了。
复杂度分析
时间复杂度
k的位数是O(log2k),对于k的每个位,需要遍历a数组来确定填的数字。因此,整个算法的时间复杂度为O(nlog2k)。
空间复杂度
在整个算法过程中只使用了有限的几个变量,因此额外空间复杂度为O(1)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
int n;
LL k, a[N];
int main() {
scanf("%d%lld", &n, &k);
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
int b = 0;
while((k>>b) > 0) b++;
LL x = 0;
bool islimit = true;
for(int j = b - 1; j >= 0; j--) {
int bit = k>>j&1;
int ub = islimit? bit: 1;
LL t1 = 0, t2 = 0;
for(int i = 1; i <= n; i++) {
t1 += (1LL ^ (a[i]>>j&1)) << j;
t2 += (0LL ^ (a[i]>>j&1)) << j;
}
if(ub == 1) {
if(t1 > t2) {
x |= 1LL << j;
if(bit == 0) {
islimit = false;
}
}else {
if(bit == 1) islimit = false;
}
}
}
LL ans = 0;
for(int i = 1; i <= n; i++) {
ans += x ^ a[i];
}
printf("%lld\n", ans);
return 0;
}