HDU 5969 最大的位或(贪心)

第一步,首先通过给的第一,第二,第三组数据,可以发现一个规律,那就是右边界肯定要算进去,比如[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;
} 

 

posted @ 2017-01-17 11:12  despair_ghost  阅读(261)  评论(0编辑  收藏  举报