首先,看到 1,2,4,8…… 这样的序列一定要想到二进制。不难发现,此题就是要我们求[a,b]的所有数中,二进制1最多的那个数的二进制有多少个1。
前置知识:__builtin_popcount:GCC中内置的函数,可用于快速计算二进制中1的个数。其对应的64位整数版本为__builtin_popcountll。
当然也可以自行实现,但以我的水平只能实现一个O(logn)的。
不难发现:如果 ⌊ l o g 2 a ⌋ ≠ ⌊ l o g 2 b ⌋ \lfloor log_2a\rfloor\neq \lfloor log_2b\rfloor ⌊log2a⌋=⌊log2b⌋,那么答案就为 ⌊ l o g 2 b ⌋ \lfloor log_2b\rfloor ⌊log2b⌋或 ⌊ l o g 2 b ⌋ \lfloor log_2b\rfloor ⌊log2b⌋+1(具体见代码)
if(lg2a!=lg2b) {
if(b+1==(1ll<<(lg2b+1))) res=lg2b+1;
else res=lg2b;
}
而二者相等时,可以发现一个偶数+1或-1得到的奇数中二进制1的个数不会减少。所以只要遍历所有奇数,并在遍历过程中更新答案即可。
if(a==b) res=__builtin_popcountll(a); //注意a==b的特殊情况
if(b%2==0) b--; //注意只遍历奇数
for(long i=b; i>=a; i-=2) { //注意数据类型为long long
int x=__builtin_popcountll(i);
res=max(res,x);
}
然而,在最坏情况下(考虑a= 2 k 2^k 2k,b= 2 k + 1 2^{k+1} 2k+1-1),这种分类讨论的复杂度可高达O(mn)(其中m=1/2,n=b-a),这还是在认为popcount函数复杂度为1的前提下。虽然提交可以通过,但这是因为数据比较水。
正确解法:从a的最低位开始,贪心地将每一位0改为1,直到a>=b
代码如下:
if(lg2a!=lg2b) {
if(b+1==(1ll<<(lg2b+1))) res=lg2b+1;
else res=lg2b;
}
else {
res=__builtin_popcountll(a);
for(int i=0; i<=lg2a&&(a|(1ll<<i))<=b; i++) {
if(!((a>>i)&1)) { //如果某一位为0,就贪心地将其改为1
a|=1ll<<i; //注意将二进制的0变为1的方法
res++;
}
}
}