===
Index
常用位操作
在下面示例中,1s
和 0s
分别表示一串1和一串0
x ^ 0s = x x & 0s = 0 x | 0s = x
x ^ 1s = ~x x & 1s = x x | 1s = 1s
x ^ x = 0 x & x = x x | x = x
获取和设置数位
1. 获取整型数值num二进制中的第i位。
将1左移i位,得到形如 00010000
的值,对这个值与num执行“位与”操作,从而将i位之外的所有位清0,检查结果是否位0.
bool getBit(int num, int i) {
return (num & (1 << i)) != 0;
}
2. 将num的第i位设置位1
int setBit(int num, int i) {
return num | (1 << i);
}
3. 将num的第i位清零
将数字 00 010 000
取反,得到类似 11 101 111
数字,对该数字与num执行与操作。
int clearBit(int num, int i) {
int mask = ~(1 << i);
return num & mask;
}
如果要清零最高位至第i位(包括最高位和第i位),先创建一个第i位为1 (1 « i) 的源码,将其减1的到得到第一部分全为0,第二部分全为1的数字,再与目标数执行与操作
int claerBitsLeft(int num, int i) {
int mask = (1 << i) - 1;
return num & mask;
}
如果要清零第i位至第0位(包括第i位和第0位),使用一串1构成的数字(-1) ,将其做一 i+1
位,得到一串第一部分全为1,第二部分全为0的数字。
int clearBitsRight(int num, int i) {
int mask = (-1 << (i + 1));
return num & mask;
}
4. 将num的第i位设置位v
首先将num的第i位清零,然后将v左移i位,得到一个i位为1,其他位为0的数,最后将两个结果执行或操作。
int updateBit(int num, int i, bool bit) {
int mask = ~(1 << i);
int v = bit ? 1 : 0;
return (num & mask) | (value << i);
}
一些有用的位操作
- 判断整数n是否为2的某次方:
n & (n -1) == 0
- 清除整数最右边的1:
n = n & (n - 1)
- 获得二进制中最低位的1:
lowbit = n & (-n)
- 得到一个数字的相反数(按位取反,再加一):
n = (~n) + 1
- « 左移乘二,» 除以2, 1«i = 2^i, x » i = x / 2^i.
二进制所有是1的位
例如: 13, 其二进制为 1101, 二进制中所有为1的位为0, 2, 3. 20, 其二进制为 10100, 二进制中所有为1的位为2, 4.
技巧: 对于任意在[0,35]中的k,2^k%37互不相等,且恰好取遍整数1-36 利用这个性质可以使用hash代替取log运算,提高效率。
vector<int> getAll1(int n) {
int H[37];
for (int i = 0; i < 36; ++i) {
H[(1ll << i) % 36] = i;
}
vector<int> res;
while (n > 0) {
res.push_back(H[(n & -n) % 37]);
n -= lowbit(n);
}
}
进制转换
1.将任意2-36进制数转化为10进制数
// s是给定的radix进制字符串
int Atoi(string s, int radix) {
int ans = 0;
for (int i = 0; i < s.size(); ++i) {
auto t = s[i];
if (t >= '0' && t <= '9')
ans = ans * radix + t - '0';
else
ans = ans * radix + t - 'a' + 10;
}
return ans;
}
strtol库函数
函数原型为 long int strtol(const char *nptr, char **endptr, int base)
base是要转化的数的进制,非法字符会赋值给endptr,nptr是要转化的字符
例如:
string s = "10100";
char * stop;
int ans = strtol(s.c_str(), &stop, 2);
// int ans = strtol(s.c_str(), NULL, 2);
2.将10进制数转换为任意的radix进制数,结果为char型
//n是待转数字,radix是指定的进制
string intToA(int n, int radix) {
string ans = "";
do {
int t = n % radix;
if (t >= 0 && t <= 9) ans += t + '0';
else ans += t - 10 + 'a';
n /= radix;
} while(n);
reverse(ans.begin(), ans.end());
return ans;
}
itoi库函数
可以将一个10进制数转换为任意的2-36进制字符串
函数原型:char* itoa(int value, char* string, int radix)
int num = 10;
char str[10];
itoa(num, str, 2); //将num转换为2进制,结果写在str中。
格雷编码
格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。
给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。即使有多个不同答案,你也只需要返回其中一种。
格雷编码序列必须以 0 开头。
1.直接计算
关键是搞清楚格雷编码的生成过程, G(i) = i ^ (i/2);
如 n = 3:
G(0) = 000,
G(1) = 1 ^ 0 = 001 ^ 000 = 001
G(2) = 2 ^ 1 = 010 ^ 001 = 011
G(3) = 3 ^ 1 = 011 ^ 001 = 010
G(4) = 4 ^ 2 = 100 ^ 010 = 110
G(5) = 5 ^ 2 = 101 ^ 010 = 111
G(6) = 6 ^ 3 = 110 ^ 011 = 101
G(7) = 7 ^ 3 = 111 ^ 011 = 100
vector<int> grayCode(int n) {
vector<int> res(1 << n);
for (int i = 0; i < (1 << n); ++i) {
res[i] = i ^ (i >> 1);
}
return res;
}
2. DFS
vector<int> res;
int vis[1<<16+2], m;
void dfs(int s, int n) {
if (res.size() == m) return;
res.push_back(s);
vis[s] = 1;
for (int i = 0; i < n; ++i) {
int c = s ^ (1 << i);
if (!vis[c]) dfs(c, n);
}
}
vector<int> grayCode(int n) {
if (!n) return {0};
memset(vis, 0, sizeof(vis));
m = 1 << n;
dfs(0, n);
return res;
}
汉明距离
整数转换,确定需要改变几个位才能将整数A转成整数B。即对应二进制不同位置的数目.
示例:
- input : 29 (11101), 15 (01111)
- output : 2
int hammingDistance(int x, int y) {
int n = x ^ y, cnt = 0;
while (n) {
n = n & (n - 1);
cnt++;
}
return cnt;
}
汉明权重
汉明权重是一串符号中不同于(定义在其所使用的字符集上的)零符号(zero-symbol)的个数。对于一个二进制数,它的汉明权重就等于它 的个数(即 popcount)。
int popcount(int x) {
int cnt = 0;
while (x) {
cnt += x & 1;
x >>= 1;
}
return cnt;
}
求一个数的汉明权重还可以使用 lowbit
操作:我们将这个数不断地减去它的 lowbit4,直到这个数变为 0。
int popcount(int x) {
int cnt = 0;
while (x) {
cnt ++;
x -= x & -x;
}
return cnt;
}
或者 使用GCC用于位运算的内建函数 ` __builtin_popcount(unsigned int x)`
构造汉明权重递增的排列
在 状压 DP 中,按照 popcount 递增的顺序枚举有时可以避免重复枚举状态。这是构造汉明权重递增的排列的一大作用。
枚举 0 ~ n 按汉明权重递增的排列
for (int i = 0; (1<<i)-1 <= n; i++) {
for (int x = (1<<i)-1, t; x <= n; t = x+(x&-x), x = x ? (t|((((t&-t)/(x&-x))>>1)-1)) : (n+1)) {
// 写下需要完成的操作
}
}
例如, 按照二进制为1的数目递增构造 0-15。
int n = (1 << 4) - 1; // 15
for (int i = 0; (1<<i)-1 <= n; i++) {
cout <<"i = " << i << ": ";
for (int x = (1<<i)-1, t; x <= n; t = x+(x&-x), x = x ? (t|((((t&-t)/(x&-x))>>1)-1)) : (n+1)) {
cout << x << " ";
}
cout << "\n";
}
输出为:
i = 0: 0
i = 1: 1 2 4 8
i = 2: 3 5 6 9 10 12
i = 3: 7 11 13 14
i = 4: 15
不用额外变量交换整数的值
void swap(int &a, int &b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
或者
void swap(int &a, int &b) { // a = 3, b = 4
a = a + b; // a = 7, b = 4
b = a - b; // a = 7, b = 3
a = a - b; // a = 4, b = 3
}
只用位运算实现整数的加减乘除
int add(int a, int b) {
int sum = a;
while (b) {
sum = a ^ b;
b = (unsigned int)(a & b) << 1;
a = sum;
}
return sum;
}
int minus(int a, int b) {// a - b = a + (-b)
return add(a, add(~b, 1));
}
乘法 a*b = a * 2^0 * b_0 + a * 2^1 * b_1 + … + a * 2^31 * b_31
int multi(int a, int b) {
int res = 0;
while (b) {
if ((b & 1) != 0)
res = add(res, a);
a <<= 1;
b >>= 1;
}
return res;
}
除法
bool isNeg(int n) {
return n < 0;
}
int getNeg(int n) {
return add(~n, 1);
}
int div(int a, int b) {
int x = isNeg(a) ? getNeg(a) : a;
int y = isNeg(b) ? getNeg(b) : b;
int res = 0;
for (int i = 31; i > -1; i = minus(i, 1)) {
if ((x >> 1) >= y) {
res |= (1 << i);
x = minus(x, y << i);
}
}
return isNeg(a) ^ isNeg(b) ? getNeg(res) : res;
}