统计二进制中1的个数和 - 位运算

来源:互联网 发布:网络下载速度很不稳定 编辑:程序博客网 时间:2024/04/16 05:28

(1)、按位与(&),将两个操作数化为二进制后并将对应的每一位分别进行逻辑与操作。(a%(2^n)=a&(2^n-1))

(2)、按位或(|),将两个操作数化为二进制后并将对应的每一位分别进行逻辑或操作。

(3)、按位异或(^),和以上同,异或是指对应位相同则运算结果为0,否则为1。

(4)、按位取反(~),对每一位进行取反。(求x的相反数:x=(~x+1))

(5)、移位。分为左移(<<)和右移(>>),左移是按照指定的位数将一个数的二进制值向左移位,左移后,低位补0,移除的高位舍去,右移相反。

等价转换:a<<n=a*(2^n),a>>n=a/(2^n)

[cpp] view plaincopyprint?
  1. #include<iostream> 
  2. using namespace std; 
  3. int n=7,m=9; 
  4. int main() 
  5. {   cout<<(n&m)<<endl;  
  6.     /********************
  7.     7:    0 0 0 0 0 1 1 1
  8.     9:(&) 0 0 0 0 1 0 0 1
  9.     _____________________
  10.           0 0 0 0 0 0 0 1 
  11.     所以输出结果为1。
  12.     ********************/  
  13.     cout<<(n|m)<<endl;  //输出(1111)2=15 
  14.     cout<<(n&1)<<endl;  //判断一个数是否为奇数,n&1=1则为奇数,否则为偶数 
  15.     cout<<(~n)<<endl;   //(11111000)2=-8,前面的的1为符号位 
  16.     cout<<(n^m)<<endl;  //(1110)2=14 
  17.     cout<<(n<<2)<<endl; //(11100)2=28 
  18.     cout<<(m>>2)<<endl; //(10)2=2  
  19.     n=n^m,m=m^n,n=n^m;  //可以用来交换两个数值 
  20.     /**********************************
  21.     n=n^m,所以m=m^n=m^(n^m)=(m^m)^n=7
  22.     n=n^m=(n^m)^(m^m)^n=(n^n)^(m^m)^m=9
  23.     **********************************/  
  24.     cout<<n<<" "<<m<<endl; 
  25.     return 0;  

位运算的应用:

求平均值:求(x+y)/2时,可能x+y会超过int的最大值,可以用位运算来求:

[cpp] view plaincopyprint?
  1. int Ave(int x,int y) 
  2. {   return x&y+((x^y)>>1); 

判断一个数是否能够写成2的幂次方形式。

[cpp] view plaincopyprint?
  1. bool comp(int x) 
  2. {   if((x&(x-1))==0) returntrue
  3.     return false
  4. }  

统计一个数的二进制数中1的个数。利用x=x&(x-1),会将x用二进制表示时最右边的一个1变为0,因为x-1会将该位变为0.

[cpp] view plaincopyprint?
  1. int Count(int x) 
  2. {   int sum=0; 
  3.     while(x) 
  4.     {   sum++; 
  5.         x=x&(x-1); 
  6.     } 
  7.     return sum; 
  8. }  

例题1:NYOJ 222(整数中的1),求区间[a,b]内所有整数的二进制的1的个数。位运算的强大应用~~~使用上面的代码的话肯定超时~~a,b的范围太大了~~呵呵,还是贴下,有些肯定用的上~~

[cpp] view plaincopyprint?
  1. #include<iostream> 
  2. #include<cstdio>  
  3. using namespace std; 
  4. int Count(int x) 
  5. {   int sum=0; 
  6.     while(x) 
  7.     {   sum++; 
  8.         x=x&(x-1); 
  9.     } 
  10.     return sum; 
  11. }  
  12. int main() 
  13. {   int a,b; 
  14.     while(scanf("%d%d",&a,&b)!=EOF) 
  15.     {   int sum=0; 
  16.         for(int i=a;i<=b;i++) 
  17.             sum+=Count(i); 
  18.         printf("%d\n",sum);      
  19.     }  
  20.     return 0; 
  21. }  

方法2:打表法,都知道是以空间换时间,直接调用效率肯定是最高的~首先我们可以保存从0~2^32中每个数的二进制1的个数,然后直接调用即可,但是这样打表似乎太困难了点,这个时候我们可以值保存0~255中的每个数的二进制值中1的个数,然后进行分段查询即可。至于这个打0~255的表的话可以利用上面的代码打出~打表的程序:

[cpp] view plaincopyprint?
  1. #include<iostream> 
  2. using namespace std; 
  3. int Count(int x) 
  4. {   int sum=0; 
  5.     while(x) 
  6.     {   sum++; 
  7.         x=x&(x-1); 
  8.     } 
  9.     return sum; 
  10. }  
  11. int main() 
  12. {   for(int i=0;i<256;i++) 
  13.         cout<<Count(i)<<","
  14.     cout<<endl;     
  15.     return 0; 
  16. }  

然后直接全选复制保存到一个数组中即可。将32位整形分为四段,求出每一段的1的个数和相加即为该数二进制值的1的个数,问题是如何求出每一段中的1的个数,比方说我要求后面k位数的1的个数,可以这样做:

[cpp] view plaincopyprint?
  1. //x   * * * * * * * * * * 
  2. //y(&)      ....1 1 1 1 1(y=2^k-1),有k个1  
  3. //_______________________ 
  4. //不管x的最后k位是什么,总之只有1&1=1,所以后面k位数与n&(2^k-1)这个数化为2进制的结果相同 

下面的代码只是分为了四段,那么数组大小定义为256=2^8即可,这个时候每次应该移走8位了~当然你还可以取其它的数,关于位运算的一些公式:

(1)、取末尾k位:x&(2^k-1);

(2)、在最后一位加一个1:x<<1+1;

(3)、把最后一位变为1:x|1;把最后一位变为0:x|1-1;

(4)、把右数第k位变为1:x|(1<<(k-1)),变为0:x&~(1<<(k-1));

(5)、把末尾k位变为1:x|(1<<k-1),变为0:x&~(1<<k-1);

(5)、去掉右起第一个1的左边|,如(100101000->1000):x & (x ^ (x-1)) 。等等~~

这个时候的代码就简单了~~

[cpp] view plaincopyprint?
  1. #include<iostream> 
  2. using namespace std; 
  3. int a[256]={0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4, 
  4.             4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5, 
  5.             4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4, 
  6.             4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6, 
  7.             4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4, 
  8.             4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5, 
  9.             4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4, 
  10.             4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, 
  11.             4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8 
  12.            }; 
  13. int Count(int n)//计算n的二进制值中1的个数 
  14. {   int sum=0; 
  15.     for(;n>0;n>>=8) 
  16.         sum+=a[n&255]; 
  17.     return sum;      
  18. }            
  19. /**********************************************
  20. int Count(int n)
  21. {   unsigned char *ptr=(unsigned char *)&n;
  22.     return a[ptr[0]]+a[ptr[1]]+a[ptr[2]]+a[ptr[3]]; 
  23. }
  24. **********************************************/  
  25. int main() 
  26. {   int a,b; 
  27.     cin>>a>>b; 
  28.     int sum=0; 
  29.     for(int i=a;i<=b;i++) 
  30.         sum+=Count(i); 
  31.     cout<<sum<<endl;    
  32.     return 0; 
  33. }  

使用这个方法的话真的是险过,但是你可以把数组做适当的调整,调大点,增加空间来降低时间,恩~~这个数组要能够写成2的幂次方的形式~

方法3:并行计算,利用二分法的思想求解~~难得想到,呵呵~我是没有想到,先贴出代码:暂时不懂,只先贴下代码,

[cpp] view plaincopyprint?
  1. #include<iostream> 
  2. using namespace std; 
  3. int Count(int x) 
  4. {   x=(x&0x55555555)+((x>>1)&0x55555555);  
  5.     x=(x&0x33333333)+((x>>2)&0x33333333);  
  6.     x=(x&0x0F0F0F0F)+((x>>4)&0x0F0F0F0F);  
  7.     x=(x&0x00FF00FF)+((x>>8)&0x00FF00FF);  
  8.     x=(x&0x0000FFFF)+((x>>16)&0x0000FFFF);  
  9.     return x; 
  10. }          
  11. int main() 
  12. {   int a,b,sum=0; 
  13.     cin>>a>>b;  
  14.     for(int i=a;i<=b;i++) 
  15.         sum+=Count(i); 
  16.     cout<<sum<<endl;   
  17.     return 0; 
  18. }  

方法4:来源于这道题目后面的讨论,这种做法就更牛了,还是看不懂,贴下~~直接刷到0秒!!!NYOJ 281(整数中的1-2)可以用这个方法过了

[cpp] view plaincopyprint?
  1. #include<iostream> 
  2. using namespace std; 
  3. int Count(int n,int m) 
  4. {   if((n>>m)==0) return 0; 
  5.     int d=(n>>m)&1; 
  6.     if(d==1) return (n>>(m+1))*(1<<m)+(n&((1<< m)-1))+1+Count(n,m+1); 
  7.     else return (n>>(m+1))*(1<<m)+Count(n,m+1); 
  8. int main() 
  9. {   int a,b; 
  10.     cin>>a>>b; 
  11.     int n=(a==0?1:a); 
  12.     cout<<Count(b,0)-Count(n-1,0)<<endl;  
  13.     return 0; 
  14. }  

例题2:NYOJ 528(找球号),充分利用^的作用:x^x=0,0^x=x。具体见代码:

[cpp] view plaincopyprint?
  1. #include<iostream> 
  2. #include<cstdio>  
  3. using namespace std; 
  4. int num,value; 
  5. int main() 
  6. {   while(scanf("%d",&num)!=EOF) 
  7.     {   int sum=0; 
  8.         for(int i=0;i<num;i++) 
  9.         {   scanf("%d",&value); 
  10.             sum^=value; 
  11.         }  
  12.         printf("%d\n",sum); 
  13.     } 
  14.     return 0;  
  15. }  

 

原创粉丝点击