题目描述
难度分:1600
输入T(≤2000)表示T组数据。
每组数据输入n(0≤n≤1018)和x(0≤x≤1018)。
输出最小的m,使得[n,m]中的所有整数的AND
等于x,注意m不能低于n。
即n∧(n+1)∧(n+2)∧…∧m=x。
如果不存在这样的m,输出−1。
输入样例
5
10 8
10 10
10 42
20 16
1000000000000000000 0
输出样例
12
10
-1
24
1152921504606846976
算法
二分答案+按位贪心
比较容易看出单调性,因为随着与的数越多,按位与的结果是单调不增的,对于一个给定的上界m:
- 如果[n,m]内所有数字按位与的结果大于x,说明m太小了,往右搜索。
- 否则m有可能是答案,往左搜索。
当二分收敛之后,检查一下此时得到的m是否满足∧mi=ni=x即可,满足就输出m,不满足就输出−1。
问题就在于怎么快速计算[left,right]中所有元素按位与的结果?这是一个经典问题,LeetCode201.数字范围按位与就是这个问题。找到left和right从高位到低位第一个不一样的位,区间内所有数按位与的结果就是从这一位开始后面都是0,前面都不变,解释如下:
令left=xxxxx0xxxxx
,right=xxxxx1xxxxx
。而xxxxx011111
>left,xxxxx100000
<right,xxxxx011111
和xxxxx100000
按位与之后就是xxxxx000000
。
复杂度分析
时间复杂度
二分答案的时间复杂度为O(log2A),本题中A=5×1018(原题中m的上界),它与n是同规模的,因此写成O(log2n)也可以。一共T组数据,因此算法整体的时间复杂度为O(T(log2n)2)(log2n最大就是60出头,每次check的常数操作为60,也是log2n级别),在极限情况下,计算量的常数操作次数为2000×602=7.2×106,是可以过的。
空间复杂度
仅使用了有限几个变量,因此额外空间复杂度为O(1)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
int t;
LL n, x;
LL rangeBitwiseAnd(LL left, LL right) {
LL ans = 0;
for(int i = 60; i >= 0; i--) {
if((left>>i&1) != (right>>i&1)) break;
if(left>>i&1) ans |= 1LL<<i;
}
return ans;
}
bool check(LL left, LL right) {
return rangeBitwiseAnd(left, right) > x;
}
int main() {
scanf("%d", &t);
while(t--) {
scanf("%lld%lld", &n, &x);
LL l = n, r = 4e18;
while(l < r) {
LL m = l + ((r - l)>>1);
if(check(n, m)) {
l = m + 1;
}else {
r = m;
}
}
if(rangeBitwiseAnd(n, r) == x) {
printf("%lld\n", r);
}else {
puts("-1");
}
}
return 0;
}