浅谈位运算
jijidawang
2020-04-05 10:56:18
目录
- 位运算前言
- 位运算应用
- 1 快速幂
- 2 最大公约数
- 3 `xor` 一些应用
- 4 其他
# 位运算前言
程序中所有东西在计算机中都是以**二进制**储存的。
位运算可以直接操作这些二进制。
所以位运算相对于普通运算要**快**。
这些是所有位运算及其优先级:
![](https://cdn.luogu.com.cn/upload/image_hosting/xfto9lqp.png)
> 注意区分逻辑运算和按位运算的区别,逻辑运算视所有 $\neq 0$ 的数为 $\texttt{True}$($1$)。
对于全体运算来说:
| 运算 |
| :--: |
| 逻辑非(`not`,`!`);按位取反(`~` ) |
| 乘法(`*`);除法(`/`);取模(`mod`,`%`)|
| 加法(`+`);减法(`-`)|
| 左移(`<<`);右移(`>>`)|
| 大于(`>`)小于(`<`),大于等于(`>=`),小于等于(`<=`)|
# 转进制IO
一些格式化符:
| 进制 | `iomanip` 格式化符 | `printf` 格式化符 |
| :---: | :---: | :---: |
| 十进制 | `dec` | `%d` |
| 八进制 | `oct` | `%o` |
| 十六进制 | `hex` | `%x` |
**注意 cin 的格式化符延长至整个程序,用 `dec` 恢复!!**
# 位运算应用
> 1. 快速幂
> 2. 最大公约数
## 1 快速幂
给定 $a,n$ 计算 $a^n$。
最朴素的做法当然是 $\mathcal{O}(n)$ 暴力乘。
```cpp
typedef long long ll;
ll power(ll a,int n)
{
ll ans=1;
for (int i=0;i<n;i++) ans*=a;
return ans;
}
```
我们优化一下。
如果我们计算 $7^{31}$。
我们做一下变式:
$\begin{aligned}
7^{31}&=7^{2^0+2^1+2^2+3^3}\\
&=7^{1+2+4+16}\\
&=7^1\times 7^2\times 7^4\times 7^{16}
\end{aligned}$
我们对于这个变式,考虑二进制拆分。
$31$ 正好分成了 $2$ 的正整数次幂,是否所有数字都可以呢?
尝试 $7^{11}$,$11=1+2+8$,发现少了个 $4$。
$11=(1101)_2$,每一个幂都乘此数的每一位即可。
$$11=2^{\color{red}{1}\color{black}\times 1}\times2^{\color{red}{1}\color{black}\times 2}\times 2^{\color{red}{0}\color{black}\times 3}\times 2^{\color{red}{1}\color{black}\times 4}$$
所以这样模拟即可,$\mathcal{O}(\log n)$ 时间复杂度。
其中 $a^n$ 可以用一个 $\texttt{base}=a^{n-1}$,$\texttt{base}=\texttt{base}*\texttt{base}$ 求得。
```cpp
typedef long long ll;
ll qpow(ll a,ll b,ll mod) //带上取模QAQ
{
ll ans=1,base=a;
while (b) //没有乘完
{
if (b&1) ans*=base,ans%=mod; //如果这一位为 1,自乘
base*=base;b>>=1;base%=mod; //更新 base,进入下一位(b>>=1)
}
return ans%mod; //如果 b=0,那么如果模数为 1 不加 %mod 就会 WA。
}
```
## 2 最大公约数
众所周知有辗转相除法:
$$\gcd(a,b)=\gcd(b,a\bmod\,b)$$
朴素算法也就是这个,**最坏**被卡 $\mathcal{O}(\log n)$。
```cpp
int gcd(int a,int b){return b?gcd(b,a%b):a;}
```
我们知道有更相减损术:
$$\gcd(a,b)=\gcd(b,a-b)$$
我们能减半尽量要减半,具体看代码即可。
```cpp
typedef long long ll;
ll gcd(ll a,ll b) //优化后
{
if (b==0) return 0; //特判
if (a<b) return gcd(b,a); //交换顺序
if (a==b) return b; //边界
if (a&1) //如果 a 是奇数
{
if (b&1) return gcd(b,a-b); //如果 b 也是奇数,只能更相减损
else return gcd(a,b>>1); //b 是偶数,减半
}
else //如果 a 是偶数
{
if (b&1) return gcd(a>>1,b); //b 是奇数,a 减半
else return 2*gcd(a>>1,b>>1); //都是偶数,一起减半,答案 *2。
}
}
```
## 3 xor 一些应用
首先有个性质:
`x^0=x`,`x^1=~x`。
`x^p^p=x`,即异或为异或的逆运算。
因异或为异或的逆运算,所以异或是唯一的可逆运算(位运算中)。
所以可以进行简单加密:
- 两人持有密钥
- 第一人将自己的数字异或密钥后传至互联网
- 第二人收到后再次异或密钥,获得原文。
***
异或还可以做一个交换:
```cpp
a=a^b;b=a^b;a=a^b;
```
缺点:
1. 慢
2. 不能处理同一个变量
总之,**别用这种交换**。
## 4 其他
- 取二进制末 $k$ 位:`a&((1<<k)-1)`;
- 取二进制第 $k$ 位:`(a>>(k-1))&1`;
- 取二进制第一个 $1$ 与后面的 $0$ 组成的数字($\texttt{lowbit}$):`x&(-x)`;
- 将最后一个 $1$ 去除:`x&=(x-1)`;
- 将右数第 $k$ 位置 $1$:`a=(a|(1<<(k-1)))`;
- 将右数第 $k$ 位置 $0$:`a&=1<<(k-1)`;
- 将右数后 $k$ 位置 $1$:`a&=(1<<(k+1))-1`;
- 将右数后 $k$ 位置 $0$:`a&=~((1<<(k+1))-1)`;
- 将右数第 $k$ 位取反:`a^=(1<<k)`;
- 将右数后 $k$ 位取反:`a^=((1<<(k+1))-1)`;
***
- `x==-1`:`~x`
- `x==0`:`!x`
- `x%2`:`x&1`