您的位置:首页 > 其它

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

2016-04-19 17:50 120 查看
题目大意:给一个数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;
}


[/code]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: