1、二进制状态压缩
二进制状态压缩,是指将一个长度为 m m m 的 bool 数组用一个 m m m 位二进制整数表示并存储的方法。
利用下列位运算操作可以实现原 bool 数组中对应下标元素的存取。
操作 | 运算 |
---|---|
取出整数 n n n 在二进制表示下的第 k k k 位 | (n >> k) & 1 |
取出整数 n n n 在二进制表示下的第 0 0 0 ~ k − 1 k - 1 k−1 位 | n & ((1 << k) - 1) |
把整数 n n n 在二进制表示下的第 k k k 位取反 | n ^ (1 << k) |
对整数 n n n 在二进制表示下的第 k k k 位赋值 1 | n | (1 << k) |
对整数 n n n 在二进制表示下的第 k k k 位赋值 0 | n & (~(1 << k)) |
这种方法运算简便,且节省了程序运行的时间和空间。当 m m m 不太大时,可以直接使用一个整数类型存储。当 m m m 较大时,可以使用若干个整数类型(int数组),也可以直接利用 C++ STL 提供的 bitset 实现。
2、成对变换
对于非负整数 n n n:
- 当
n
n
n 为偶数时,
n ^ 1
等于 n + 1 n + 1 n+1 - 当
n
n
n 为偶数时,
n ^ 1
等于 n − 1 n - 1 n−1
因此,“0 与 1” “2 与 3” “4 与 5” … 关于 ^ 1
运算构成 “成对变换”。
这一性质经常用于图论邻接表中边集的存储。在具有无向边(双向边)的图中把一对正反方向的边分别存储在邻接表数组的第
n
n
n 与
n
+
1
n+1
n+1 位置(其中
n
n
n 为偶数),就可以通过 ^ 1
的运算获得与当前边
(
x
,
y
)
(x, y)
(x,y) 反向的边
(
y
,
x
)
(y, x)
(y,x) 的存储位置。
3、lowbit运算
lowbit(n) 定义为非负整数 n n n 在二进制表示下 “最低位的 1 及其后边所有的 0” 构成的数值。 如 n = 10 n = 10 n=10 的二进制表示为 ( 1010 ) 2 (1010)_2 (1010)2,则 l o w b i t ( n ) = 2 = ( 10 ) 2 lowbit(n) = 2 = (10)_2 lowbit(n)=2=(10)2。
推导 l o w b i t ( n ) lowbit(n) lowbit(n) 的公式。
设 n > 0 n > 0 n>0, n n n 的第 k k k 位是 1,第 0 0 0 ~ k − 1 k-1 k−1 位都是 0。
为了实现 lowbit 运算,先把 n n n 取反,此时的第 k k k 为变成 0,第 0 0 0 ~ k − 1 k-1 k−1 位都是1。再令 n = n + 1 n = n + 1 n=n+1,此时因为进位,第 k k k 为变为1,第 0 ~ k − 1 k-1 k−1 位都是0。
在上面取反加1操作后,
n
n
n 的第
k
+
1
k+1
k+1 位到最高位恰好与原来相反,所以 n & (~n+1)
仅有第
k
k
k 位为1,其余位都是0。而在补码表示下,~n = -1 - n
,因此
lowbit(n) = n & (~n + 1) = n & (-n)
lowbit 运算配合 Hash 可以找出整数二进制表示下所有是 1 的位,所花费的时间与 1 的个数同级。 为达该目的,只需不断把 n 赋值为 n - lowbit(n),直至 n = 0 n=0 n=0。
例如,n = 9 = (1001)2,则 lowbit(9) = 1。把 n 赋值为 9 - lowbit(9) = 8 = (1000)2,则 lowbit(8) = 8 = (1000)2。此时 8 - lowbit(8) = 0,停止循环。在整个过程中减掉了 1 和 8 = (1000)2 两个数,它们恰好是 n 每一位上的 1 后面补 0 得到的数值。取 1 和 8 的对数 log21 和 log28,即可得知 n n n 的第 0 位和第 3 位是 1。 因为 C++ math.h 库的 log 函数是以 e 为底的实数运算,并且复杂度常数较大,所以需要预处理一个数组,通过 Hash 的方法代替 log 运算。
当 n 较小时,最简单的方法是直接建立一个数组 H,令 H[2k] = k,程序如下:
const int MAX_N = 1 << 20;
int H[MAX_N + 1];
for (int i = 0; i <= 20; i++) H[1 << i] = i; //预处理
while (cin >> n) {
while (n > 0) {
cout << H[n & -n] << ' ';
n -= n & -n;
}
cout << endl;
}
稍微复杂但效率更高的方法是建立一个长度为 37 的数组 H,令 H[2 k mod 37] = k。这里利用了一个小的数学技巧: ∀ k ∈ [ 0 , 35 ] , 2 k \forall k \in [0,35], 2^k ∀k∈[0,35],2k mod 37 互不相等,且恰好取遍1 ~ 36。需要之后的程序为:文章来源:https://www.toymoban.com/news/detail-717290.html
int H[37];
for (int i = 0; i < 36; i++) H[(1ll << i) % 37] = i;
while (cin >> n) {
while (n > 0) {
cout << H[n & -n] << ' ';
n -= n & -n;
}
cout << endl;
}
值得指出的是,lowbit运算也是树状数组中的一个基本运算。文章来源地址https://www.toymoban.com/news/detail-717290.html
到了这里,关于【位运算】二进制状态压缩、成对变换、lowbit运算的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!