干净又实用的位运算备忘——涵盖位操作的语义、若干常用恒等式与经典技巧(含代码示例和注意事项)。适合刷题/写底层代码/竞赛时速查。
基础语义(按位操作)
- &:按位与(AND)。1&1=1,其余为0。用于掩码(mask)/清位。
- |:按位或(OR)。至少有1则为1。用于置位/合并标志。
- ^:按位异或(XOR)。相同为0,不同为1。用于无进位加法、交换两个数、不重复元素。
- ~:按位取反(NOT)。- ~x= 按位翻转(注意有符号整型含符号位)。
- <<:左移(乘2的幂,低位补0)。
- >>:右移(逻辑/算术取决于语言/类型,注意符号扩展)。
- -n:负数(在二进制中通常以 二补码 表示),性质:- -n = ~n + 1。
二进制与二补码(关键恒等)
- 负数表示(两补):-n == (~n) + 1。
 例:若n = 5 (00000101),则-n = 11111011(8位表示)。
- n & (n - 1):清除最低位的 1(把- n的最低位1置为0)
 证明直观:- n-1把最低 1 变 0,并把其右边的 0/1 翻转,因此与操作清掉那个 1。
 用途:计数二进制 1 的个数(见下)。
- n & (-n):提取最低位的 1(最低位 1 的掩码)
 例:- n = 12 (1100)->- n & -n = 0100 (4)。
- n ^ (n - 1):最低位 1 及其右边全部变成 1(形如- ...00111...)。常用来构造掩码。
- ~n与- -n的组合常用于位运算构造或取反掩码:清第 k 位:- n & ~(1<<k)。
经典技巧与代码(C / C++ / Python 表示)
1) 判断是否为 2 的幂
bool isPow2(unsigned n) {
    return n && !(n & (n - 1));
}
(零不是幂;n & (n-1) == 0 当且仅当 n 是 2 的幂)
2) 计数二进制 1(Brian Kernighan 算法)
int popcount(unsigned n) {
    int cnt = 0;
    while (n) {
        n &= (n - 1); // 清掉最低位的 1
        cnt++;
    }
    return cnt;
}
3) 提取最低位的 1
unsigned lowbit(unsigned n) {
    return n & -n; // 等价于 n & (~n + 1)
}
4) 清掉最低位的 1(得到 n 的下一个子集)
unsigned remove_lowest_one(unsigned n) {
    return n & (n - 1);
}
5) 枚举一个集合的所有非空子集(位掩码技巧)
// 遍历 s 的所有子集 t (包括空集,若要排除空集可处理)
for (unsigned t = s; t; t = (t - 1) & s) {
    // 处理子集 t
}
6) 交换两个整数(无临时变量,用 XOR)
a ^= b;
b ^= a;
a ^= b;
(仅当 a 与 b 不是同一地址时安全;否则会变 0)
7) 设置 / 清除 / 切换 / 读取第 k 位
// set k-th bit
n |= (1u << k);
// clear k-th bit
n &= ~(1u << k);
// toggle k-th bit
n ^= (1u << k);
// get k-th bit (0/1)
int bit = (n >> k) & 1;
8) 构造从 0 到 k 连续的低位掩码
// mask with lowest k bits set: (1<<k) - 1
unsigned mask = (1u << k) - 1;  // 当 k==32 要小心溢出(使用 64 位)
9) 判断两个数是否异号(常用于溢出检测)
bool diff_sign(int x, int y) {
    return (x ^ y) < 0;
}
(最高位不同则异号)
10) 向上/向下对齐到 2 的幂(对齐常见)
// 向上对齐 x 到 2^k(假设 x>0 且 k 是幂)
unsigned align_up(unsigned x, unsigned a) {
    return (x + a - 1) & ~(a - 1); // a 必须是幂
}
常见题型与位运算解法思路(刷题指南)
- 找出只出现一次/两次/三次的数:使用 XOR / 位按位统计(对每一位计数 mod k)。
- 子集枚举与状态压缩 DP:位掩码遍历、子集转移常用 (s & -s)、s & (s-1)。
- 最低位相关操作:n & -n、n & (n-1)是常见 O(1) 小技巧。
- 位 DP 中使用 popcount(mask)或预计算__builtin_popcount(GCC/Clang)加速状态代价计算。
注意事项与陷阱
- 有符号右移:>>对有符号类型通常是算术右移(保持符号),对无符号则做逻辑右移。题目中若依赖位模式,建议使用unsigned。
- 优先级:位运算优先级低于加减、移位高于比较等;用括号避免歧义:n & (n - 1)。
- 溢出/类型宽度:1 << 31在 32 位int上可能是 UB/移位边界;尽量使用1u或更宽类型1ULL。
- XOR 交换的局限:当 a 和 b 指向同一内存(例如同一个变量的别名)时会出错;也会降低可读性,现代编译器优化下用临时变量更清晰。
- ~ 运算与负数:~n与-n都依赖位宽,注意在打印/比较时与有符号/无符号的差别。
小结(记住这几条)
- -n = ~n + 1(两补码)
- n & (n - 1)—— 清最低位 1
- n & -n—— 提取最低位 1(最低 1 的掩码)
- n && !(n & (n - 1))—— 判断是否为 2 的幂
- 枚举子集:for (t = s; t; t = (t - 1) & s)
- 位运算适合:计数、掩码、状态压缩、加速常数操作
发表回复