置1:x | (1 << k)
置0:x & ~(1 << k)
取反:x ^ (1 << k)
将最后一个有效位置零:x & (x - 1)
将有效位后面的所有0置1:x | (x - 1)
判断是否是2的幂次: x & (x - 1) == 0
判断奇偶性: x & 1
补码:
一个数的补码是将lowbit保留下来,比lowbit高的位进行取反。
因此我们有恒等式:~x = -x - 1
,同时也是lowbit函数的原理。
逻辑位组:
将二进制数每一位看做是一个bool变量,通过位移等运算进行置位等。
例如,在生成不重复排列中,我们可以通过检查该位上是否出现过该字母,通过逻辑位组实现。性能优于bool数组。
class Solution
{
public:
vector<string> ans;
void permutation(string &s, int idx)
{
if (idx == s.size())
{
ans.push_back(s);
return;
}
int vis = 0;
for (int i = idx; i < s.size(); i++)
{
if (((vis >> (s[i] - 'a')) & 1) == 0)
{
swap(s[idx], s[i]);
permutation(s, idx + 1);
swap(s[idx], s[i]);
vis |= (1 << (s[i] - 'a'));
}
}
}
vector<string> permutation(string s)
{
permutation(s, 0);
return ans;
}
};
GCC内置函数 _builtin:
此外,这些函数都有相应的usigned long和usigned long long版本,只需要在函数名后面加上l或ll就可以了,比如int __builtin_clzll。
注意,只在GCC环境下可用。
lowbit:
lowbit返回最低位的 1 1 1。
int lowbit(int x)
{
return x & -x;
}
二分法计数1的个数:
int popcount(x)
{
x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF);
return x;
}
汉明距离:
Hamming’s distance是两个二进制串中不同位的个数,即__builtin_popcount(a ^ b)
。
枚举与子集类:
二进制子集表示法
每一个二进制位都代表一个元素,如果该位为1,表示元素存在,否则元素不存在。
因此,我们得到以下运算:
集合运算 | 二进制运算 |
---|---|
a ∩ b a \cap b a∩b | a & b |
a ∪ b a \cup b a∪b | a | b |
a ˉ \bar{a} aˉ | ~a |
a − b a - b a−b | a & (~b) |
枚举二进制子集:
将最后一个1置0,然后将后面的子集元素全部置1,从头开始,可以保证枚举的是所有的子集。
for (int i = stats; i; i = (i - 1) & stats)
{
// Code
}
枚举大小固定的子集:
Gosper’s Hack是一种生成 n n n元集合所有 k k k元子集的算法,它巧妙地利用了位运算。这里我们先给出代码:
void GospersHack(int k, int n)
{
int cur = (1 << k) - 1;
int limit = (1 << n);
while (cur < limit)
{
// do something
int lb = cur & -cur;
int r = cur + lb;
// cur = ((r ^ cur) >> __builtin_ctz(lb) + 2) | r;
cur = (((r ^ cur) >> 2) / lb) | r;
}
}
异或前缀和:
定义异或前缀和函数 sumXor ( x ) \text{sumXor}(x) sumXor(x),表示 0 ⊕ 1 ⊕ 2 ⊕ ⋯ ⊕ 0 \oplus 1 \oplus 2 \oplus \dots \oplus 0⊕1⊕2⊕⋯⊕。
连续四个相邻整数的异或和有:
∀ i ∈ Z , 4 i ⊕ ( 4 i + 1 ) ⊕ ( 4 i + 2 ) ⊕ ( 4 i + 3 ) = 0 \forall i \in Z,4i \oplus (4i+1) \oplus (4i+2) \oplus (4i+3) = 0 ∀i∈Z,4i⊕(4i+1)⊕(4i+2)⊕(4i+3)=0
因此,我们有:
sumXor ( x ) = { x , x = 4 k , k ∈ Z ( x − 1 ) ⊕ x , x = 4 k + 1 , k ∈ Z ( x − 2 ) ⊕ ( x − 1 ) ⊕ x , x = 4 k + 2 , k ∈ Z ( x − 3 ) ⊕ ( x − 2 ) ⊕ ( x − 1 ) ⊕ x , x = 4 k + 3 , k ∈ Z \text{sumXor}(x)= \begin{cases} x,& x=4k,k\in Z\\ (x-1) \oplus x,& x=4k+1,k\in Z\\ (x-2) \oplus (x-1) \oplus x,& x=4k+2,k\in Z\\ (x-3) \oplus (x-2) \oplus (x-1) \oplus x,& x=4k+3,k\in Z\\ \end{cases} sumXor(x)=⎩⎪⎪⎪⎨⎪⎪⎪⎧x,(x−1)⊕x,(x−2)⊕(x−1)⊕x,(x−3)⊕(x−2)⊕(x−1)⊕x,x=4k,k∈Zx=4k+1,k∈Zx=4k+2,k∈Zx=4k+3,k∈Z
具体的化简:
sumXor ( x ) = { x , x = 4 k , k ∈ Z 1 , x = 4 k + 1 , k ∈ Z x + 1 , x = 4 k + 2 , k ∈ Z 0 , x = 4 k + 3 , k ∈ Z \text{sumXor}(x)= \begin{cases} x,& x=4k,k\in Z\\ 1,& x=4k+1,k\in Z\\ x+1,& x=4k+2,k\in Z\\ 0,& x=4k+3,k\in Z\\ \end{cases} sumXor(x)=⎩⎪⎪⎪⎨⎪⎪⎪⎧x,1,x+1,0,x=4k,k∈Zx=4k+1,k∈Zx=4k+2,k∈Zx=4k+3,k∈Z
异或前缀和:
类比于前缀和,异或前缀和能实现区间查询,不修改数组。
LeetCode 1310
class Solution
{
public:
vector<int> xorQueries(vector<int> &arr, vector<vector<int>> &queries)
{
vector<int> psum;
psum.push_back(0);
for (int i : arr)
{
psum.push_back(psum.back() ^ i);
}
vector<int> ans;
for (vector<int> &query : queries)
{
ans.push_back(psum[query[1] + 1] ^ psum[query[0]]);
}
return ans;
}
};
取顶或取底:
ll fdiv(ll a, ll b) { return a / b - ((a ^ b) < 0 && a % b); }
ll cdiv(ll a, ll b) { return a / b + ((a ^ b) > 0 && a % b); }