(水)POJ-2453 位运算(必看!)

题目大意:给一个数n,输出一个数m且m满足:

①n,m的二进制表示中1的个数相同(如00000001(1)与00000010(2))

②m为满足m>n的最小值(如当n=1时,m=2正确而m=4错误)


题目链接:点击打开链接


分析:这个题就是一个单纯的位运算的题,当然,我只会用朴素的方法来解决。可是看了下别人的代码才发现这个题的位运算是多么的强大!朴素的方法相信大家都会,就是简单的枚举(每次+1),判断是否满足条件①就行了,第一次满足条件的肯定是最小的。


下面给大家来说说真正的位运算。。


根据贪心的思想,可以想到若要求一个最小的满足条件的,那么肯定要将最右边的01中的1前移一位即变成10并且将其右边的1都往右移到最低位(我们可以这么理解,因为m的1个数与n相同,那么肯定是移动了n的某些1变成了m,那么要使m>n则肯定有1的左移,而又要求最小,则优先左移最右边的1,而某个位上的1可以左移的条件是此位的前一位为0,假如我们找到这个01将其变成10之后,它是否就是最小的了呢?显然这是不对的,例如0110变成了1010之后,1001<1010,所以1010并不是最优解,而是要再将原来的01后面的1全移动到最右边去),明白了这个就往下看。


首先,学过树状数组就应该知道n&-n的意义,例如n为01001110时,n&-n为00000010。让x=n&-n=00000010。


然后,n+x的数值为在n的二进制中从右往左找到第一个01并将这个0与它后面的1取反码,所以n+x=01010000,记y=n+x,这个时候我们已经将最右边的01变成了10,副作用是将原来01后面的1都变成了0,0保持不变,所以得想办法再补上这几个1,且补在最低位上。


接下来,怎么才能求出副作用中失去的1呢?由于副作用将01变成了10,将01之后的1变成了0,01之后的0则保持不变,所以我们想到了异或,因为异或可以将对立的(0与1对立)一组变成1,而恰巧,y与n对立的位就只有n中从右到左的第一个01与01之后的1所对应的位,所以n^y得到00011110,记t=n^y。观察t与x,发现t/x刚好将00011110变成了00001111。由于00001111中有2个1是由01与10异或产生的,而实际上只需要补上01之后失去的1就行了,所以我们再将00001111右移2位除掉2个多余的1得到00000011。


最终n+x+00000011便是所求的解了。


附上朴素版与高级版代码:

#include<iostream>    //朴素版  164K 266Ms
int main() 
{
	int n;
	while (scanf("%d", &n) && n)
	{
		int Sum = 0;
		for (int i = 0; i < 32; i++)
			if (n >> i & 1) Sum++;
		for (n++;; n++)
		{
			int sum = 0;
			for (int i = 0; i < 32; i++)
				if (n >> i & 1) sum++;
			if (sum == Sum)
			{
				printf("%d\n", n);
				break;
			}
		}
	}
	return 0;
}

#include<iostream>   //高级版  164K 0Ms
int main()
{
	int n;
	while (scanf("%d", &n) && n)
	{
		int x = n&-n;
		printf("%d\n", n + x + ((n ^ (n + x)) / x >> 2));
	}
	return 0;
}




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值