給定自然數l和r ,選取2個整數x,y滿足l <= x <= y <= r ,使得x|y最大。
其中|表示按位或,即C、 C++、 Java中的|運算。
第一行有一個正整數,表示數據的組數。
接下來每一行表示一組數據,包含兩個整數l,r。
保證 0 <= l <= r <= 1018。
2016年中國大學生程序設計競賽(合肥)-重現賽(感謝安徽大學)
貪心,但是我做了好久,可以說自己是個智障。
首先通過給的第一,第二,第三組數據,可以發現一個規律,那就是右邊界肯定要算進去,比如【1,10】那麼10一定要算進去。
爲什麼呢?我們可以稍微證明一下:假設右邊界爲x,考慮x-1.第一種情況,如果x-1的二進制位數比x少一位,那麼無疑應該選x;
如果x-1的二進制位數和x相同,那麼給(x-1)加上的1肯定會使一個0變成1,我們考慮一下也應該選x。
然後數學歸納一下,就能得證了。
接下來第二步,如何確定第二個數?這個問題很玄學,經過樣例數據,我們得到這樣一個算法:
sum=0;
for(int i=length;i>=0;i--)//length爲兩個二進制數長度的最大值
{
if(l[i]==r[i])
{
if(l[i]==1)
sum+=(long long)pow(2,i);
}
else
{
sum+=(long long)pow(2,i+1)-1;
break;
}
}
簡單的說,對於我們要求的另一個數y,從高位到低位,找到第一個位置兩個二進制數不同的,然後該位取0,後面全部取1.之前的位數照抄。
下面簡單證明一下。
首先對於之前的每個位置i,因爲兩個二進制數的第i位都是相同的,那麼我們取的數第i位肯定也是這個數,這一點很好理解。
接下來是比較難的理解的一部分:假設【left,right】爲題目要求的區間,那麼對於第一個兩個二進制數不同的位置j,肯定是一個0,一個1。易證0屬於left,1屬於right。
接下來,對於y的第j位,我們無論取0或者1,對於最後的結果是沒有影響的(因爲right的第j位爲1)。那麼,如果我們第j位取0,之後的所有位取1,我們可以證明這是最優解,而且y在【left,right】之間。
最後貼代碼:
#include<bits/stdc++.h>
using namespace std;
int t,r[70],l[70];
unsigned long long ll,rr;
int get_r(long long x)
{
int index=0;
memset(r,0,sizeof(r));
while(x)
{
r[index]=x%2;
x/=2;
index++;
}
return index-1;
}
int get_l(long long x)
{
int index=0;
memset(l,0,sizeof(l));
while(x)
{
l[index]=x%2;
x/=2;
index++;
}
return index-1;
}
int main(void)
{
unsigned long long sum;
int length,length_l,length_r;
cin>>t;
while(t--)
{
cin>>ll>>rr;
length_l=get_l(ll);
length_r=get_r(rr);
length=max(length_l,length_r);
sum=0;
for(int i=length;i>=0;i--)
{
if(l[i]==r[i])
{
if(l[i]==1)
sum+=(long long)pow(2,i);
}
else
{
sum+=(long long)pow(2,i+1)-1;
break;
}
}
cout<<sum<<endl;
}
return 0;
}
有一個坑點,就是1<<i 當i太大時,要寫成1LL<<i
否則會爆int
我這裏採用了pow然後類型轉換