位运算 之(1) 按位与(AND)& 操作【转载】

本文介绍了位运算的基本原理及其在计算机科学中的应用,包括整数奇偶性判断、2的幂判断、模运算等实用技巧。

由于位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。

按位与(Bitwise AND),运算符号为&

a&b 的操作的结果:a、b中对应位同时为1,则对应结果位也为1、

例如:

10010001101000101011001111000

&             111111100000000  

---------------------------------------------

                                   10101100000000

对10101100000000进行右移8位得到的是101011,这就得到了a的8~15位的掩码了。那么根据这个启示,判断一个整数是否是处于 0-65535 之间(常用的越界判断):

用一般的 (a >= 0) && (a <= 65535) 可能要两次判断。

改用位运算只要一次:

a & ~((1 << 16)-1)

后面的常数是编译时就算好了的。其实只要算一次逻辑与就行了。

 

             

常用技巧:

1、  用于整数的奇偶性判断

一个整数a, a & 1 这个表达式可以用来判断a的奇偶性。二进制的末位为0表示偶数,最末位为1表示奇数。使用a%2来判断奇偶性和a & 1是一样的作用,但是a & 1要快好多。

2、  判断n是否是2的正整数冪

(!(n&(n-1)) ) && n

举个例子:                                               

如果n = 16 = 10000, n-1 = 1111

那么:

10000

& 1111

----------

                          0

再举一个例子:如果n = 256 = 100000000, n-1 = 11111111

那么:

100000000

&11111111

--------------

        0

好!看完上面的两个小例子,相信大家都有一个感性的认识。从理论上讲,如果一个数a他是2的正整数幂,那么a 的二进制形式必定为1000…..(后面有0个或者多个0),那么结论就很显然了。

3、  统计n1的个数

朴素的统计办法是:先判断n的奇偶性,为奇数时计数器增加1,然后将n右移一位,重复上面步骤,直到移位完毕。

朴素的统计办法是比较简单的,那么我们来看看比较高级的办法。

举例说明,考虑2位整数 n=11,里边有21,先提取里边的偶数位10,奇数位01,把偶数位右移1位,然后与奇数位相加,因为每对奇偶位相加的和不会超过两位,所以结果中每两位保存着数n1的个数;相应的如果n是四位整数 n=0111,先以一位为单位做奇偶位提取,然后偶数位移位(右移1位),相加;再以两位为单位做奇偶提取,偶数位移位(这时就需要移2位),相加,因为此时没对奇偶位的和不会超过四位,所以结果中保存着n1的个数,依次类推可以得出更多位n的算法。整个思想类似分治法。
在这里就顺便说一下常用的二进制数:

0xAAAAAAAA=10101010101010101010101010101010

0x55555555 = 1010101010101010101010101010101(奇数位为1,1位为单位提取奇偶位

0xCCCCCCCC = 11001100110011001100110011001100

0x33333333 =    110011001100110011001100110011(以“2为单位提取奇偶位)

0xF0F0F0F0 = 11110000111100001111000011110000

0x0F0F0F0F =     1111000011110000111100001111“8为单位提取奇偶位

0xFFFF0000 =11111111111111110000000000000000               

0x0000FFFF =                 1111111111111111“16为单位提取奇偶位

例如:32位无符号数的1的个数可以这样数:
 

复制代码
int count_one(unsigned long n)
{
    
//0xAAAAAAAA,0x55555555分别是以“1位”为单位提取奇偶位
    n = ((n & 0xAAAAAAAA>> 1+ (n & 0x55555555);

    
//0xCCCCCCCC,0x33333333分别是以“2位”为单位提取奇偶位
    n = ((n & 0xCCCCCCCC>> 2+ (n & 0x33333333);

    
//0xF0F0F0F0,0x0F0F0F0F分别是以“4位”为单位提取奇偶位
    n = ((n & 0xF0F0F0F0>> 4+ (n & 0x0F0F0F0F);

    
//0xFF00FF00,0x00FF00FF分别是以“8位”为单位提取奇偶位
    n = ((n & 0xFF00FF00>> 8+ (n & 0x00FF00FF);

    
//0xFFFF0000,0x0000FFFF分别是以“16位”为单位提取奇偶位
    n = ((n & 0xFFFF0000>> 16+ (n & 0x0000FFFF);

    
return n;
}
复制代码

  

举个例子吧,比如说我的生日是农历2月11,就用211吧,转成二进制:

                     n = 11010011

计算n = ((n & 0xAAAAAAAA) >> 1) + (n & 0x55555555);

得到              n = 10010010

计算n = ((n & 0xCCCCCCCC) >> 2) + (n & 0x33333333);

得到              n = 00110010

计算n = ((n & 0xF0F0F0F0) >> 4) + (n & 0x0F0F0F0F);

得到              n = 00000101 -----------------à无法再分了,那么5就是答案了。

  

4、对于正整数的模运算注意,负数不能这么算

先说下比较简单的:

乘除法是很消耗时间的,只要对数左移一位就是乘以2,右移一位就是除以2,传说用位运算效率提高了60%。

乘2^k 众所周知: n<<k。所以你以后还会傻傻地去敲2566*4的结果10264吗?直接2566<<2就搞定了,又快又准确。

除2^k众所周知: n>>k。

那么 mod 2^k 呢?(2的倍数取模

n&((1<<k)-1)

用通俗的言语来描述就是,对2的倍数取模,只要将数与2的倍数-1做按位与运算即可。

好!方便理解就举个例子吧。

思考:如果结果是要求模2^k时,我们真的需要每次都取模吗?

在此很容易让人想到快速幂取模法。

快速幂取模算法

经常做题目的时候会遇到要计算 a^b mod c 的情况,这时候,一个不小心就TLE了。那么如何解决这个问题呢?位运算来帮你吧。

首先介绍一下秦九韶算法:(数值分析讲得很清楚)

把一个n次多项式f(x) = a[n]x^n+a[n-1]x^(n-1)+......+a[1]x+a[0]改写成如下形式:

  f(x) = a[n]x^n+a[n-1]x^(n-1))+......+a[1]x+a[0]

  = (a[n]x^(n-1)+a[n-1]x^(n-2)+......+a[1])x+a[0]

  = ((a[n]x^(n-2)+a[n-1]x^(n-3)+......+a[2])x+a[1])x+a[0]

  =. .....

  = (......((a[n]x+a[n-1])x+a[n-2])x+......+a[1])x+a[0].

  求多项式的值时,首先计算最内层括号内一次多项式的值,即

  v[1]=a[n]x+a[n-1]

  然后由内向外逐层计算一次多项式的值,即

  v[2]=v[1]x+a[n-2]

  v[3]=v[2]x+a[n-3]

  ......

  v[n]=v[n-1]x+a[0]

这样,求n次多项式f(x)的值就转化为求n个一次多项式的值。

好!有了前面的基础知识,我们开始解决问题吧

由(a × b) mod c=( (a mod c) × b) mod c.

我们可以将 b先表示成就:

  b = a[t] × 2^t + a[t-1]× 2^(t-1) + …… + a[0] × 2^0.  (a[i]=[0,1]).

这样我们由 a^b  mod  c = (a^(a[t] × 2^t  +  a[t-1] × 2^(t-1) + …a[0] × 2^0) mod c.

然而我们求  a^( 2^(i+1) ) mod c=( (a^(2^i)) mod c)^2 mod c .求得。

具体实现如下:

使用秦九韶算法思想进行快速幂模算法,简洁漂亮

复制代码
// 快速计算 (a ^ p) % m 的值
__int64 FastM(__int64 a, __int64 p, __int64 m)

    
if (p == 0return 1;
    __int64  r 
= a % m;
    __int64  k 
= 1;
    
while (p > 1)
    {
        
if ((p & 1)!=0)
        {
            k 
= (k * r) % m; 
}
              r 
= (r * r) % m;
            p 
>>= 1;
        }
        
return (r * k) % m;
}
复制代码

 http://acm.pku.edu.cn/JudgeOnline/problem?id=3070

5、计算掩码

比如一个截取低6位的掩码:0×3F
用位运算这么表示:(1 << 6) - 1
这样也非常好读取掩码,因为掩码的位数直接体现在表达式里。

按位或运算很简单,只要a和b中相应位出现1,那么a|b的结果相应位也为1。就不多说了。 

6、子集

  枚举出一个集合的子集。设原集合为mask,则下面的代码就可以列出它的所有子集: 


 

  for (i = mask ; i ; i = (i - 1) & mask) ; 

  很漂亮吧。

Description 【问题描述】 对于 1 二进制变量定义两种运算运算运算规则 &oplus; 0&oplus;0=0 0&oplus;1=1 1&oplus;0=1 1&oplus;1=1 &times; 0&times;0=0 0&times;1=0 1&times;0=0 1&times;1=1 运算的优先级是: 1. 先计算括号内的,再计算括号外的。 2. &ldquo;&times;&rdquo;运算优先于&ldquo;&oplus;&rdquo;运算,即计算表达式时,先计算&times;运算,再计算&oplus;运算。 例如:计算表达式 A&oplus;B&times;C 时,先计算 B&times;C,其结果再与A做&oplus;运算。 现给定一个未完成的表达式,例如 _+(_*_),请你在横线处填入数字0或者1,请问有多少种填法可以使得表达式的值为0。 Input 输入文件共2 行。 第1 行为一个整数 L,表示给定的表达式中除去横线外的运算符和括号的个数。 第2 行为一个字符串包含 L 个字符,其中只包含&rsquo;(&rsquo;、&rsquo;)&rsquo;、&rsquo;+&rsquo;、&rsquo;*&rsquo;这4 种字符,其中(&rsquo;、&rsquo;)&rsquo;是左右括号,&rsquo;+&rsquo;、&rsquo;*&rsquo;分别表示前面定义的运算&ldquo;&oplus;&rdquo;和&ldquo;&times;&rdquo;。这行字符按顺序给出了给定表达式中除去变量外的运算符和括号。 Output 输出共1 行。包含一个整数,即所有的方案数。注意:这个数可能会很大,请输出方案数对 10007取模后的结果。 Sample Input 4+(*) Sample Output 3 &mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash; 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.youkuaiyun.com/jnxxhzz/article/details/53510336
最新发布
06-03
### 二进制运算表达式填数问题分析 要解决该问题,需要明确以下几个关键点: 1. **表达式的结构**:给定的表达式可能包含二进制运算符(如 `&amp;`, `|`, `^` 等)以及未知变量。这些未知变量可以被填入特定值以满足表达式结果为0。 2. **二进制运算规则**:根据引用中的内容[^3],可以得知常见的二进制运算规则,例如按位与 (`&amp;`)、按(`|`) 和按异或 (`^`)操作方式。 3. **动态规划或回溯法**:由于需要计算所有可能的填法数量,因此可以采用动态规划或回溯算法来枚举所有可能的情况,并统计满足条件的结果。 以下是一个通用的解决方案,基于动态规划的思想实现: #### 动态规划解法 ```python MOD = 10007 def count_ways(expression, target): # 解析表达式为操作符和操作数 def parse_expression(expr): tokens = [] i = 0 while i &lt; len(expr): if expr[i].isdigit(): num = 0 while i &lt; len(expr) and expr[i].isdigit(): num = num * 2 + int(expr[i]) i += 1 tokens.append(num) elif expr[i] in (&#39;&amp;&#39;, &#39;|&#39;, &#39;^&#39;): tokens.append(expr[i]) i += 1 else: i += 1 return tokens # 动态规划核心函数 def dp(tokens, start, end): if (start, end) in memo: return memo[(start, end)] if start == end: return {tokens[start]: 1} ways = {} for mid in range(start + 1, end, 2): op = tokens[mid] left_ways = dp(tokens, start, mid - 1) right_ways = dp(tokens, mid + 1, end) for lv, lc in left_ways.items(): for rv, rc in right_ways.items(): if op == &#39;&amp;&#39;: result = lv &amp; rv elif op == &#39;|&#39;: result = lv | rv elif op == &#39;^&#39;: result = lv ^ rv if result not in ways: ways[result] = 0 ways[result] = (ways[result] + lc * rc) % MOD memo[(start, end)] = ways return ways tokens = parse_expression(expression) memo = {} result_ways = dp(tokens, 0, len(tokens) - 1) return result_ways.get(target, 0) # 示例调用 expression = &quot;x&amp;y|z&quot; target = 0 result = count_ways(expression, target) print(result) ``` #### 代码说明 1. **解析表达式**:通过 `parse_expression` 函数将字符串形式的表达式解析为操作符和操作数的列表。 2. **动态规划递归**:使用 `dp` 函数递归地计算子表达式的可能结果及其对应的填法数量。 3. **结果统计**:最终返回满足目标值(这里是0)的所有填法数量,并对结果取模10007。 #### 注意事项 - 表达式中可能包含未知变量(如 `x`, `y`, `z`),这些变量需要被替换为具体的二进制值进行计算。 - 如果表达式较为复杂,可以通过增加缓存机制(如 `memo` 字典)优化性能。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值