C++ 几种常用位运算简介
-
按位与
a & b
法则:两者相同位都为 1,则结果中该位为 1;否则结果中该位为 0- 例:12 & 6 = 4
12:1 1 0 0
6:0 1 1 0
4:0 1 0 0
- 例:12 & 6 = 4
-
按位或
a | b
法则:两者相同位中有一个为 1,则结果中该位为 1;否则结果中该位为 0- 例:12 | 6 = 14
12:1 1 0 0
6:0 1 1 0
14:1 1 1 0
- 例:12 | 6 = 14
-
按位异或
a ^ b
法则:两者相同位的值若不同,则结果中该位为 1;否则结果中该位为 0- 例:12 ^ 6 = 10
12:1 1 0 0
6:0 1 1 0
10:1 0 1 0
- 例:12 ^ 6 = 10
-
按位取反
~a
法则:该数中 0 的位置变为 1;1 的位置变为 0
(这个涉及二进制补码,先不例) -
按位左移
a << b
法则:将该数 a 左移 b 位,正数左移后为正数,负数左移后为负数- 例:3 << 2 = 12
3:0 0 1 1
12:1 1 0 0
a<<b=a×2b
- 例:3 << 2 = 12
-
按位右移
a >> b
法则:将该数 a 右移 b 位,正数右移后为正数,负数右移后为负数。不论正负,都下取整- 例:13 >> 2 = 3
13:1 1 0 1
3:0 0 1 1
a>>b=a/2b
- 例:13 >> 2 = 3
-
非
!a
法则:该数是 0 则为 1;否则为 0- 例:!0 = 1
!1 = 0
!2 = 0
- 例:!0 = 1
各种运算符优先级
-
,!
,~
> *
,/
,%
> >>
,<<
> >
,<
,>=
,<=
> ==
,!=
> &
> ^
> |
> &&
> ||
> 问号表达式 > 赋值语句
负数表示
用补码表示负数:
- \because -1 = 0 - 1
\ \ \ \ \ \ \ 0 = 000 \cdots 00
\ \ \ \ \ \ \ 1 = 000 \cdots 01
\therefore -1 = 111 \cdots 11
同理:
- -2 = 111 \cdots 110
-3 = 111 \cdots 101
-4 = 111 \cdots 100
\cdots
-x = ~x + 1
算补上上面那个例了8
八进制与十六进制
八进制数每一位用 0 \sim 7 表示,每一位占二进制中的 3 位
在 C++ 中,前面加 0
表示八进制数,例:
printf("%d\n", 015);
// 输出:13
十六进制数每一位用 0 \sim f 表示(a \sim f 分别表示 10 \sim 15),每一位占二进制中的 4 位
在 C++ 中,前面加 0x
表示十六进制数,例:
printf("%d\n", 0xd);
// 输出:13
字节
一个字节为 8 位二进制数,也是2 位十六进制数,即 00000000 \sim 11111111
一个 int
占 4 个字节,即 32 位二进制数
一个 long long
占 8 个字节,即 64 位二进制数
memset
按字节赋值,故当 f
为 int
型数组时, memset(f, 0x3f, sizeof f)
是将 f
赋值为 0x3f3f3f3f
位运算常用技巧
(多用于状压 DP)
将 x 第 i 位取反:x ^= 1 << i
将 x 第 i 位制成 1:x |= 1 << i
将 x 第 i 位制成 0:x &= -1 ^ 1 << i
或 x &= ~(1 << i)
取 x 对 2 取模的结果:x & 1
取 x 的第 i 位是否为 1:x & 1 << i
或 x >> i & 1
取 x 的最后一位:x & -x
取 x 的绝对值:(x ^ x >> 31) - (x >> 31)
(int 型)
判断 x 是否不为 2 的整次方幂:x & x - 1
判断 a 是否不等于 b:a != b
,a - b
,a ^ b
判断 x 是否不等于 -1:x != -1
,x ^ -1
,x + 1
,~x
应用
枚举子集
- 枚举一个集合 S 的子集 O(2 ^ {|S|}) (|S| 表示 S 中的元素个数)
// S 是一个二进制数,表示一个集合,i 枚举 S 的所有子集
for (int i = S; i; i = S & i - 1)
// blablabla
- 枚举集合 {0, 1, \cdots, n - 1} 的所有子集的子集 O(3 ^ n)
for (int S = 0; S < 1 << n; S ++ ) // 枚举集合 {0, ..., n - 1} 的所有子集
for (int i = S; i; i = S & i - 1) // 枚举子集 S 的子集
// blablabla
交换两个数字
void swap(int &a, int &b)
{
a ^= b;
b ^= a;
a ^= b;
}
求二进制中数字 1 的个数
- 暴力 O(\log x)
int count(int x)
{
int res = 0;
while (x) res += x & 1, x >>= 1;
return res;
}
- 针对 1 较稀疏的情况的优化 O(\log x)
int count(int x)
{
int res = 0;
while (x) x &= x - 1, res ++ ;
return res;
}
- 位运算 O(1)
int count(int x)
{
x = (x >> 1 & 0x55555555) + (x & 0x55555555);
x = (x >> 2 & 0x33333333) + (x & 0x33333333);
x = (x >> 4 & 0x0f0f0f0f) + (x & 0x0f0f0f0f);
return x % 255;
}
求 \log _ 2 x
- 暴力 O(\log x)
int log_2(int x)
{
int res = 0;
while (x >> 1) res ++ , x >>= 1;
return res;
}
- 二分 O(\log\log x)
int log_2(int x)
{
int l = 0, r = 31, mid;
while (l < r)
{
mid = l + r + 1 >> 1;
if (x >> mid) l = mid;
else r = mid - 1;
}
return r;
}
- 位运算(二分展开)O(1)
int log_2(int x)
{
int res = 0;
if (x & 0xffff0000) res += 16, x >>= 16;
if (x & 0xff00) res += 8, x >>= 8;
if (x & 0xf0) res += 4, x >>= 4;
if (x & 0xc) res += 2, x >>= 2;
if (x & 2) res ++ ;
return res;
}
求一遍从 1 到 10 ^ 7 的 \log _ 2 x,暴力要 470ms,二分要 177ms,位运算要 70ms,<cmath> 库的那个要 307ms当然人家那是带精度的咱不能跟它比
预处理 1 \sim n 中所有数 \log 后的结果 O(n)
方法一
int log_2[N]; // 存 log2(i) 的结果
void init(int n)
{
for (int i = 0; 1 << i <= n; i ++ )
log_2[1 << i] = i;
for (int i = 1; i <= n; i ++ )
if (!log_2[i])
log_2[i] = log_2[i - 1];
}
方法二
int log_2[N]; // 存 log2(i) 的结果
void init(int n)
{
for (int i = 1; i <= n; i ++ )
log_2[i] = log_2[i - 1] + ((1 << log_2[i - 1]) == i);
for (int i = 1; i <= n; i ++ )
log_2[i] -- ;
}
快读优化
inline int read()
{
int x = 0; bool f = false; char ch = getchar();
while (ch < 48 || ch > 57) {f |= (ch == 45); ch = getchar();}
while (ch > 47 && ch < 58) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return f ? ~x + 1 : x;
}
龟速加
int add(int a, int b)
{
while (b)
{
int x = a ^ b;
b = (a & b) << 1;
a = x;
}
return a;
}
例题
状态压缩DP\ \ \ \ AcWing 91. 最短Hamilton路径\ \ \ \ 题解
位运算性质\ \ \ \ AcWing 998. 起床困难综合征\ \ \ \ 题解
二进制枚举\ \ \ \ AcWing 116. 飞行员兄弟\ \ \ \ 题解(挂份秦大佬的先)
qp!
交换两个数可以写成一行的。
a ^= b ^= a ^= b;
大佬,按位或那里的例子写错了吧?
例:12 & 6 = 14
应该是
例:12 | 6 = 14
大神,请教一下,
// S 是一个二进制数,表示一个集合,i 枚举 S 的所有子集
for (int i = S; i; i = S & i - 1)
这是什么原理,可以麻烦解释一下吗?
不太好解释。模拟一下试试?
点赞点赞
点赞 , 收藏
抽风是妹妹?不对吧?
好问题。
资瓷!点赞!收藏!
zc,tql
抽风大佬太强了
抽风大佬tql,收藏收藏
tql
cfnb
抽风妹子,牛逼!
抽风是男的吧。。。
%%%% 右移符号好像反了
抱歉,已改~
Orz 收藏啦
谢谢资瓷~
抽风妹妹好棒
cf大佬判断 x 是否不等于 −1的最后一个是
~x
吧抱歉手残了,已改~
快读优化的原理是撒啊,@垫底抽风 大佬
二进制运算快啊
哦哦哦我在看看i了i了,太厉害了,这个板子我背了
orzzzzz