hdu 5969 最大的位或 貪心



Problem Description
B君和G君聊天的時候想到了如下的問題。
給定自然數l和r ,選取2個整數x,y滿足l <= x <= y <= r ,使得x|y最大。
其中|表示按位或,即C、 C++、 Java中的|運算。
 

Input
包含至多10001組測試數據。
第一行有一個正整數,表示數據的組數。
接下來每一行表示一組數據,包含兩個整數l,r。
保證 0 <= l <= r <= 1018
 

Output
對於每組數據輸出一行,表示最大的位或。
 

Sample Input
5 1 10 0 1 1023 1024 233 322 1000000000000000000 1000000000000000000
 

Sample Output
15 1 2047 511 1000000000000000000
 

Source

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然後類型轉換

發佈了104 篇原創文章 · 獲贊 12 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章