位运算简介

1. 基本运算

1. 按位或( ∣ |

与运算位于 C++ 运算符的第十二级。该运算把数字转成了二进制,按照每一位操作,规则如下:

  1. 两个都是 0 0 0,结果为 0 0 0
  2. 两个都是 1 1 1,结果为 1 1 1
  3. 一个是 1 1 1,一个是 0 0 0,结果为 1 1 1

也就是说,只要有一个是 1 1 1,结果就是 1 1 1,否则就是 0 0 0
例如: 170 ∣ 85 170|85 17085
转成二进制,分别是 10101010 10101010 10101010 1010101 1010101 1010101

 10101010
|01010101
---------
 11111111

∴ 170 ∣ 85 = 11111111 ( 2 ) = 255 \therefore170|85=11111111(2)=255 17085=11111111(2)=255
满足交换律,结合律。
a ∣ a = a a|a=a aa=a
a ∣ 0 = a a|0=a a0=a

2. 按位异或(^)

按位异或位于 C++ 运算符的第十一级,英文又写成 XOR,一般数学中写成 ⊕ \oplus ,同样是每一位操作,规则如下:

  1. 两个都是 0 0 0,结果为 0 0 0
  2. 两个都是 1 1 1,结果为 0 0 0
  3. 一个是 1 1 1,一个是 0 0 0,结果为 1 1 1

也就是说,相同为 0 0 0,不同为 1 1 1,或者也可以理解为二进制中的不进位加法(因此其符号为 ⊕ \oplus )。
例如: 170 ⊕ 85 170\oplus85 17085

 10101010
^01010101
---------
 11111111

∴ 170 ⊕ 85 = 11111111 ( 2 ) = 255 \therefore170\oplus85=11111111(2)=255 17085=11111111(2)=255
注意,尽管我们在数学中一般写作 ⊕ \oplus ,但在 C++ 程序中,依然要写成 ^。
满足交换律,结合律。
a ⊕ a = 0 a\oplus a=0 aa=0
a ⊕ 0 = a a\oplus 0=a a0=a

3. 按位与( & \& &

按位与位于 C++ 运算符的第十级,同样是每一位操作,规则如下:

  1. 两个都是 0 0 0,结果为 0 0 0
  2. 两个都是 1 1 1,结果为 1 1 1
  3. 一个是 1 1 1,一个是 0 0 0,结果为 0 0 0

也就是说,只要有一个是 0 0 0,结果就是 0 0 0,否则就是 1 1 1
例如: 170 & 85 170\&85 170&85

 10101010
&01010101
---------
 00000000

∴ 170 & 85 = 0 \therefore170\&85=0 170&85=0
满足结合律,交换律。
a & a = a a\&a=a a&a=a
a & 0 = 0 a\&0=0 a&0=0

4. 位左移( < < << <<

位左移位于 C++ 运算符的第七级,其将该数的二进制每一位都左移一些位,右边的空余用 0 0 0 补齐,左边超出的位会被覆盖掉。例如:
111 ( 2 ) < < 1 = 1110 ( 2 ) 111(2)<<1=1110(2) 111(2)<<1=1110(2)
如果是八位二进制整数,那么有:
111 ( 2 ) < < 6 = 11000000 111(2)<<6=11000000 111(2)<<6=11000000
被覆盖掉了一个 1 1 1
事实上,如果 x x x y y y 都是 z z z 位二进制整数,那么有 x < < y = 2 y x   m o d   2 z x<<y=2^yx\bmod 2^z x<<y=2yxmod2z因此,位左移可以帮助我们轻松地计算 2 n 2^n 2n(即 1 < < n 1<<n 1<<n)。

5. 位右移( > > >> >>

位左移位于 C++ 运算符的第七级,其将该数的二进制每一位都右移一些位,超出的位会被覆盖掉。例如:
111 ( 2 ) > > 1 = 11 ( 2 ) 111(2)>>1=11(2) 111(2)>>1=11(2)
事实上, x > > y = ⌊ x 2 y ⌋ x>>y=\lfloor\frac{x}{2^y}\rfloor x>>y=2yx

6. 按位取反(~)

按位取反位于 C++ 运算符的第三级,将二进制的每一位都取反,即:

  1. 1变成0
  2. 0变成1

例如 ~ 1010101 ( 2 ) 1010101(2) 1010101(2)= 101010 ( 2 ) 101010(2) 101010(2)
满足结合律:~ ( ( (~ a ) = a a)=a a)=a

7. 复合运算符

二进制中的混合运算符一般有下面五个:
& = \&= &=:与等于。 a & = b ⇔ a = a & b a\&=b\Leftrightarrow a=a\&b a&=ba=a&b
∣ = |= =:或等于。 a ∣ = b ⇔ a = a ∣ b a|=b\Leftrightarrow a=a|b a=ba=ab
^ = = =:异或等于。 a a a^ = b ⇔ a = a =b\Leftrightarrow a=a =ba=a^ b b b
< < = <<= <<=:左移等于。 a < < = b ⇔ a = a < < b a<<=b\Leftrightarrow a=a<<b a<<=ba=a<<b
> > = >>= >>=:右移等于。 a > > = b ⇔ a = a > > b a>>=b\Leftrightarrow a=a>>b a>>=ba=a>>b
这五个运算符全部位于 C++ 运算符的第十六级。

2. 常用应用

二进制中有一些很重要的组合运算,很容易出。

1. l o w b i t lowbit lowbit

l o w b i t ( x ) lowbit(x) lowbit(x) 指的是 x x x 的二进制表示中,最低位的 1 1 1 所对应的值。例如, l o w b i t ( 12 ) = l o w b i t ( 1100 ( 2 ) ) = 2 2 = 4 lowbit(12)=lowbit(1100(2))=2^2=4 lowbit(12)=lowbit(1100(2))=22=4
一般,比较常用的计算 l o w b i t ( x ) lowbit(x) lowbit(x) 的公式有两个,分别是
l o w b i t ( x ) = x & − x lowbit(x)=x\&-x lowbit(x)=x&x

l o w b i t ( x ) = x & ( x ⊕ ( x − 1 ) ) lowbit(x)=x\&(x\oplus(x-1)) lowbit(x)=x&(x(x1))
其中公式二就出现在了 CSP-S1 2019 中。
l o w b i t lowbit lowbit 最主要的用处是在树状数组中。

2. gcd ⁡ \gcd gcd

gcd ⁡ \gcd gcd 指的是最大公约数,一般我们用辗转相除法会写成这样:

int gcd(int a,int b)
{
	if(b==0) return a;
	return gcd(b,a%b);
}

然而,事实上,我们还可以写成这样:

int gcd(int a,int b)
{
	while(b^=a^=b^=a%=b);
	return a;
}

不用担心爆栈,看上去也更清爽。

__gcd(a,b);

……

3. 快速幂

快速幂需要快速求解
b p   m o d   k b^p\bmod k bpmodk
一般,我们写递归会写成这个样子:

long long quickpow(long long b,long long p,long long k)
{
	if(p==1) return b%k;
	if(p==0) return 1%k;
	long long ans=quickpow(b,p/2,k);
	if(p%2==0) return (ans%k)*(ans%k)%k;
	if(p%2==1) return (ans%k)*(ans%k)%k*b%k;
}

还有的会写递推。但是,真正的王者还是位运算:

long long quickpow(long long b,long long p,long long k)
{
	long long ans=1%k;
	b%=k;
	while(p)
	{
		if(p&1) ans=(ans*b)%k;
		p>>=1;
		b=(b*b)%k;
	}
	return ans;
}

4. 集合运算

我们可以将一个自然数集合 S S S 压缩成一个二进制数 s s s。该二进制数为
s = ∑ i ∈ S 2 i s=\sum_{i\in S}2^i s=iS2i如果转成了二进制,那么一些集合操作就能够更好地实现。
设有两集合 A A A B B B,已经转成了二进制数 a a a b b b,则

  1. 空集 ⇒ \Rightarrow 0
  2. 只含有第 i i i 个元素的集合 { i } ⇒ \{i\}\Rightarrow {i}1<<i
  3. 含有全部 n n n 个元素的集合 { 0 , 1 , 2 , . . . , n − 1 } ⇒ \{0,1,2,...,n-1\}\Rightarrow {0,1,2,...,n1}(1<<n)-1
  4. 判断第 i i i 个元素是否在集合 A A A ⇒ \Rightarrow if(a>>i&1)
  5. 向集合 A A A 中加入第 i i i 个元素 ⇒ \Rightarrow a|1<<i
  6. 从集合 A A A 中取出第 i i i 个元素 ⇒ \Rightarrow a&~(1<<i)
  7. A ∩ B ⇒ A\cap B\Rightarrow AB a&b
  8. A ∪ B ⇒ A\cup B\Rightarrow AB a|b

枚举集合 S S S 的每一个子集时,只需要这样

int sub=s;
do sub=(sub-1)&s;
while(sub!=s);

每个 s u b sub sub 都是 S S S 的子集,且没有缺漏。

5. 状态压缩 DP

状态压缩动态规划,就是我们俗称的状压 DP,是利用计算机二进制的性质来描述状态的一种 DP 方式。
很多棋盘问题都运用到了状压,同时,状压也很经常和 BFS 及 DP 连用。
状压 DP 其实就是将状态压缩成 2 2 2 进制来保存 其特征就是看起来有点像搜索,每个格子的状态只有 1 1 1 0 0 0,一类非常典型的动态规划。

6. 补码

二进制负数在计算机中使用补码存储。
假设 a a a 是一个二进制正整数,那么 − a -a a 在计算机中的补码是 ~ a + 1 a+1 a+1。例如, − 1 -1 1 的八位二进制补码是 11111111 11111111 11111111
知道就好。

7. 其他杂碎

假如有二进制数 x x x,那么

操作运算
去掉最后一位x>>1
在最后加一个 0 0 0x<<1
在最后加一个 1 1 1(x<<1)+1
最后一位变 1 1 1x|1
最后一位变 0 0 0x|1-1
最后一位取反x^1
右数第 k k k 位变 1 1 1x|1<<k-1
右数第 k k k 位变 0 0 0x&~(1<<k-1)
右数第 k k k 位取反x^1<<k-1
k k kx&1<<k-1
右数第 k k kx>>k-1&1
k k k 位变成 1 1 1x|(1<<k-1)
k k k 位取反1^(1<<k-1)
把右边连续的 1 1 1 0 0 0x&x+1
把右起第一个 0 0 0 1 1 1x|x+1
把右边连续的 0 0 0 1 1 1x|x-1
取右边连续的 1 1 1(x^x+1)>>1
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值