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; }