首先感谢这位前辈的博客……这里详细说下自己的理解。
http://blog.csdn.net/qq_33402621/article/details/53047895
这题主要是思路……
输入两个整数l和r,求两个数满足l<=x<=y<=r,使得x | y最大。
另ans = x | y,那么ans肯定的位数肯定和r一样,因为位数越多,这个数就越大,位数最多的肯定是r。
所以我们先把r用二进制表示,把每一位存在一个数组里面。
然后从二进制的最右边开始(代码里面因为是每次把这个数的二进制最右边的数拿出来放到数组ans的第0,1,2,3的位置,所以遍历的时候从0~ansCnt)遍历。
遇到某一位是1,说明这一位已经可以取到1,不用管;遇到0,说明这一位要试试能不能让它进行或操作后变成1。
怎么试呢?一个个遍历?数据范围是10的18次方,肯定会超时。
所以我们这样,假如ans数组的第i个元素是0, 求一个数num =r - sum - 1,sum表示ans中从0到i - 1的数字表示的值,比如ans的前4位是1,0, 1,0,那么i=2的时候,sum就是
101这个二进制数对应是十进制数,也就是5 。
这个num就是与之进行或操作后可以让这一位变成1的,举个例子。假如r的二进制是 11110111,现在要求能使那个0变成1的数,那么所求的数肯定那一位就必须是1,所求的数
就是r - sum - 1,sum 就是111,r - sum = 11110000,然后再-1,11101111,可以看到对应的那一位是1 。
这个num一定比r小,因为sum >= 0,而num = r - sum - 1
这样求出来的num是 满足对应位是1 且 小于r 的数里面最大的。因为求num的时候,r - sum,r左边的几位都没有动,也不能动,只是右边全变成了0,然后-1,这样右边全变
成了1,也就是最大,全1当然最大,对吧?(如果把前面的几位也变了,会不会比num更大且满足条件呢?不会的,因为如果把前面的0变成了1,那么肯定就比r大了,如果把前
面的1变成0,又肯定比num小。举个不太恰当的十进制的例子,r=2345012,要把0变成9,那么应该是2345012 - 12 - 1 = 2344999,如果把前面的2345任何一个变大了,就肯定比r大,如果把4变成了3,那么得2335999,比2344999小……)
为什么要求出最大的满足条件的数呢?因为要和 l(输入的数据) 比较,如果最大的能使这一位变成1的数字都比l小,说明没有数能使这一位变成1 。
如果num >= l,说明这一位可以变成1,就把ans数组的这一位变成1 l;否则就不管
然后看下一位。
最后ans所表示的数就是答案。
可能有人怀疑,这样每次算的num都不同,而题目规定只能取两个数,会不会不对?
不会的。因为求最左边那个0的时候,i = a, num的0到a - 1位都变成了1 。
那为什么不一开始就求最左边的0呢,因为这时求出来的num可能比l小,还是要看其他位。
#include<iostream>
#define LEN (64 + 5)
using namespace std;
long long l, r;
int ans[LEN];
int ansCnt;
long long mi[LEN];
long long opMi(int num)
{
if(mi[num])
return mi[num];
long long sum = 1;
for(int i=1; i<=num; i++)
sum *= 2;
return mi[num] = sum;
}
int main()
{
int t;
int i;
cin>>t;
while(t--)
{
ansCnt = 0;
cin>>l>>r;
long long temp = r;
while(temp)
{
ans[ansCnt++] = temp & 1;
temp >>= 1;
}
long long sum = 0;
for(i=0; i<ansCnt; i++)
{
if(ans[i] == 0)
{
if(r - sum - 1 >= l)
ans[i] = 1;
}
else
sum += opMi(i);
}
sum = 0;
for(i=0; i<ansCnt; i++)
if(ans[i])
sum += opMi(i);
cout<<sum<<endl;
}
return 0;
}