介绍
位运算是对整数在二进制层面进行操作的运算方式。计算机内部所有数据都是以二进制形式存储的,所以位运算是计算机处理数据最底层、最快速的一种方式。 在竞赛中,位运算可以大幅提高效率,常用于:
- 状态压缩(DP)
- 异或技巧
- 快速判断特定位
位运算符对照表
| 运算符 | 名称 | 示例(对二进制) | 说明 |
|---|---|---|---|
& | 与(AND) | 0101 & 0011 = 0001 | 两个都是1,结果才是1 |
| | 或(OR) | 0101 | 0011 = 0111 | 有一个是1就为1 |
^ | 异或(XOR) | 0101 ^ 0011 = 0110 | 不同为1,相同为0 |
! | 逻辑非(Logical NOT) | !123 = 0 , !0=1 | 如果值为0,结果为 1.如果值为非0,结果为 0 |
~ | 非(NOT) | ~0101 = 1010(取反) | 所有位取反 |
<< | 左移 | 0101 << 1 = 1010 | 向左移一位,相当于乘2 |
>> | 右移 | 0101 >> 1 = 0010 | 向右移一位,相当于除2 |
| 功能 | 示例 | 式子 |
|---|---|---|
| 去掉最后一位 | 10110 → 1011 | x >>> 1 |
| 在最后添加 0 | 1011 → 10110 | x << 1 |
| 在最后添加 1 | 1011 → 10111 | (x << 1) | 1 |
| 右数第 k 位变成 1 | 100011 → 101011,k = 4 | x | (1 << (k - 1)) |
| 右数第 k 位变成 0 | 101011 → 100011,k = 4 | x & ~(1 << (k - 1)) |
| 获取右数第 k 位 | 101011 → 1,k = 4 | (x >> (k - 1)) & 1 |
| 截取最后 k 位 | 101011 → 1011,k = 4 | x & ((1 << k) - 1) |
| 把右边连续的 1 变成 0 | 101011 → 101000 | x & (x + 1) |
| 把右边第一个 0 变成 1 | 101011 → 101111 | x | (x + 1) |
| 把右边连续的 0 变成 1 | 101000 → 101111 | x | (x - 1) |
| 把右起第一个 1 变成 0 | 101000 → 100000 | x & (x - 1) |
| 取右边连续的 1 | 101111 → 1111 | (x ^ (x + 1)) >> 1 |
常用板子
判断一个数是否是2的幂次。
bool f(int x)
{
if( x & (x-1) == 0 ) return true;
return false;
}
计算一个数2进制有多少个1。
int f(int x)
{
int cnt=0;
while(x)
{
x-=x&(-x);
cnt++;
}
return cnt;
}
得到二进制最右边的1和后边0所构成的数。
int lowbit(int x)
{
return x&(-x);
}
遍历所有非空子集,并输出每个集合的所有子集
void f(int n)
{
for(int i=1;i<(1<<n);i++) // 枚举所有非空集合 i(共 2^n 个)
{
for(int j=i;j;j=(j-1)&i) // 枚举 i 的所有非空子集 j
{
cout << j << " "; // 输出子集 j(是二进制状态)
}
cout << endl;
}
}
枚举超集的代码
void f(int n)
{
for(int i=1; i<(1<<n); i++) // 枚举所有非空子集 i(从1到2^n-1)
{
for(int j=i; j; j=(j+1) | i) // 从 j=i 开始,不断生成包含 i 的更大集合 j
{
if(j == (1<<n)-1 ) break; // 如果已经到全集(所有位为1),就退出
cout << j << " "; // 输出 j 的整数表示
}
cout << endl; // 每个 i 对应一行输出
}
}