1223:An Easy Problem
题目的要求是:
要使得 n 在 a 不变的情况下 ,得到任意多的 n' 满足 n' > n ,定义为集合 M ,要求取 M 中最小的 n' 为
(a 代表 n 中二进制位 为 1 的个数)
一种暴力做法是,统计 n 的二进制中 1 的个数为 a ,然后 设置一个变量 t = n,t 每次自增 1 (t++),
之后,统计 t 的二进制中 1 的个数为 b , 如果 a == b,那么 t 就是 比 n 大的正整数集合 M 中,最小的数。
O(1) 的做法是用位运算,举个例子 :
n == 78,我们尝试着在不改变 a 的情况下,移动 n 的二进制中的 1 , 让 n 变大的幅度尽可能的小
序号 n n 中的二进制表示 1 78 0100 1110 2 77 0100 1101 3 89 0101 0101 4 83 0101 0011 为了实现变大的过程,肯定要把 某个 1 向左移动,那么如何选取这个 1 呢?
从右边开始 寻找第一段 连续的 1 (只有一个 1 也算 一段), 把将这段连续 1 中最左端的 1 向左前进 一 位
要让这段连续 1 中的左端 前进一位 ,只要在 这段连续的 1 的右端 加 1就好了,二进制的加法进位到头
如何寻找 右端的 1 呢?
: t1 = n & (-n) 得到最右边的 1
0100 1110
& 1011 0010
= 0000 0010
之后 t2 = n + t1; 得到进位之后的值。
0100 1110
+ 0000 0010
= 0101 0000
但是,这么做了之后,我们就丢失了 原先那段连续 的 1 的信息了。
为了让 n 变大的 幅度尽可能的小
就要把那一段连续的 1 剩余的部分,全部移动到 最右边 (参考 序号 1 和 序号 4 的二进制表示)
寻找那段丢失的 1 。
n ^ t2
0100 1110
^ 0101 0000
= 0001 1110
我们发现,这样 经过 ^ 得到的结果,会多出两个 1 ,解法办法是,右移的时候,再向右移动两位就好
即 >> 2
(n ^ t2) / t1 ,( / t1) 这个操作是为了把 ^ 之后的 1 整个向右移动 \(log_2 t1\) 的位置,
因为 t1 代表了 最右边的 1 ,且只有 1 ,在二进制表示中,肯定是 2的幂,\(log_2 t1\) 得到当前t1 中 1的位置
/ t1 相当于 >>\(log_2 t1\) ,两者相等, 之后再 >>2 消去多余的 两个 1 ,
最后 和 t2 取 按位或 就得到了最终想要的结果。
代码实现
#includeusing namespace std;int main(){ ios::sync_with_stdio(0); cin.tie(0),cout.tie(0); int x,t1,t2,y; while(cin>>x){ if(x == 0) break; t1 = x & (-x); t2 = x + t1; y = t2 | ((x ^ t2) / t1) >> 2; cout< <
参考: