第一步,首先通过给的第一,第二,第三组数据,可以发现一个规律,那就是右边界肯定要算进去,比如[1,10]那么10一定要算进去。
即得最后结果中或值最大的两个数中的第一个数x;
为什么呢?我们可以稍微证明一下:假设右边界为r,考虑r-1.第一种情况,如果r-1的二进制位数比r少一位,那么无疑应该选r;如7(0111)和8(1000)
如果r-1的二进制位数和x相同,那么给(r-1)加上的1肯定会使一个0变成1,我们考虑一下也应该选r;如8(1000)和9(1001)
然后数学归纳一下,就能得证了。
接下来第二步,如何确定第二个数?这个问题很玄学,经过样例数据,我们得到这样一个算法:
long long sum=0;
int maxlen=max(i,j); // 1 1 0 1 0
for(int k=maxlen-1;k>=0;k--){ // | 1 0 0 0 1
if(s1[k]==s2[k]){ // = 1 0 1 1 1
if(s1[k]==1)
sum+=(long long)pow(2,k);
}
else{//1+2^1+2^2+...+2^k=2^(k+1)-1<2^(k+1)
sum+=(long long)pow(2,k+1)-1;
break;
}//该位取0,后面可以全部用1补充,因为不会超过下限l和上限r,属于[l,r]范围内
}
简单的说,对于我们要求的另一个数y,从高位到低位,找到第一个位置两个二进制数不同的,然后该位取0,后面全部取1,之前的位数照抄。
下面简单证明一下。
首先对于之前的每个位置i,因为两个二进制数的第i位都是相同的,那么我们取的数第i位肯定也是这个数,因为要尽量取最大值,这一点很好理解。
接下来是比较难的理解的一部分:假设[left,right]为题目要求的区间,那么对于第一个两个二进制数不同的位置j,肯定是一个0,一个1。易证0属于left,1属于right。
接下来,对于y的第j位,我们无论取0或者1,对于最后的结果是没有影响的(因为right的第j位为1,该位或值肯定为1)。那么,如果我们对于y的第j位取0,之后的所有位取1,我们可以证明这是最优解,因为x|y尽量接近…1111…而且y在[left,right]之间;如果取1,则y的后面如上所述全部取1的话,则有y>r,不合题意。
完整代码:
//参考 http://m.blog.csdn.net/article/details?id=53400225
//首先证明必须 r 肯定要算进去 比如[1,10]那么10一定要算进去
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
int s1[100];
int s2[100];
int main(){
int t;
scanf("%d",&t);
while(t--){
long long l;
long long r;
scanf("%lld %lld",&l,&r);
if(l==r)
printf("%lld\n",l);
else{
memset(s1,0,sizeof(s1));
memset(s2,0,sizeof(s2));
int i=0;
int j=0;
while(l!=0){
int t=l%2;
s1[i++]=t;
l/=2;
}
while(r!=0){
int t=r%2;
s2[j++]=t;
r/=2;
}
long long sum=0;
int maxlen=max(i,j); // 1 1 0 1 0
for(int k=maxlen-1;k>=0;k--){ // | 1 0 0 0 1
if(s1[k]==s2[k]){ // = 1 0 1 1 1
if(s1[k]==1)//坑点
sum+=(long long)pow(2,k);
}
else{//1+2^1+2^2+...+2^k=2^(k+1)-1<2^(k+1)
sum+=(long long)pow(2,k+1)-1;
break;
}//该位取0,后面可以全部用1补充,因为不会超过下限l和上限r,属于[l,r]范围内
}
printf("%lld\n",sum);
}
}
return 0;
}