前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《算法竞赛进阶指南》0x01 位运算

《算法竞赛进阶指南》0x01 位运算

作者头像
一只野生彩色铅笔
发布2022-10-31 10:54:55
4310
发布2022-10-31 10:54:55
举报

Bit Twiddling Hacks

位运算基础概念

基本的位运算共 6 种,分别为按位与、按位或、按位异或、按位取反、左移和右移

运算

运算符

解释

&

只有两个对应位都为 \(1\) 时才为 \(1\)

|

只要两个对应位有一个 \(1\) 时就为 \(1\)

异或

^

只有两个对应位不同时才为 \(1\)

取反

~

对二进制表示的每一位取反(有符号数的符号位也会取反)

左移

<<

对二进制表示向左移动 \(1\) 位所得的值

右移

>>

对二进制表示向右移动 \(1\) 位所得的值

1

时才为

1

| 只要两个对应位有一个

1

时就为

1

异或 ^ 只有两个对应位不同时才为

1

取反 ~ 对二进制表示的每一位取反(有符号数的符号位也会取反) 左移 << 对二进制表示向左移动

1

位所得的值 右移 >> 对二进制表示向右移动

1

位所得的值

运算符优先级

加减

移位

比较大小

位与

异或

位或

+ -

<< >>

> < == !=

&

^

|

二进制常用操作

操作

运算

取出整数 n 在二进制表示下的第 k 位

(n >> k) & 1

取出整数 n 在二进制表示下的第 0 ~ k-1 位 (后 k 位)

n & ((1 << k) - 1)

把整数 n 在二进制表示下的第 k 位取反

n xor (1 << k)

对整数 n 在二进制表示下的第 k 位赋值 1

n | (1 << k)

对整数 n 在二进制表示下的第 k 位赋值 0

n & (~(1 << k))

位运算的应用

基本分类:

  1. 高效地进行某些运算,代替其他低效的方式
  2. 表示集合(常用于 状态压缩DP
  3. 题目本来就要求进行位运算

2 的幂次相关

将一个数乘(除) 2 的非负整数次幂

向下取整,而非向零取整

代码语言:javascript
复制
int mulPowerOfTwo(int n, int m) // 计算 n*(2^m)
{
  return n << m;
}
int divPowerOfTwo(int n, int m) // 计算 n/(2^m)
{
  return n >> m;
}

判断一个数是不是 2 的非负整数次幂

二进制表示中只有一位 1

代码语言:javascript
复制
bool isPowerOfTwo(int n)
{
    return n > 0 && (n & (n - 1)) == 0;
}

对 2 的非负整数次幂取模

保留那一位以内的所有二进制

代码语言:javascript
复制
int modPowerOfTwo(int x, int mod)
{
    return x & (mod - 1);
}

模拟集合操作

操作

集合表示

位运算语句

交集

\(a \cap b\)

a & b

并集

\(a \cup b\)

a | b

补集

\(\overline{a}\)

~a

差集

\(a \setminus b\)

a & (~b)

对称差

\(a \Delta b\)

a ^ b

a \cap b

a & b 并集

a \cup b

a | b 补集

\overline{a}

~a 差集

a \setminus b

a & (~b) 对称差

a \Delta b

a ^ b

子集遍历

时间复杂度:

O(2^{popcount(u)})

而遍历一个集合所有子集的子集,时间复杂度为

O(3^n)

(每个元素只有三中状态)

代码语言:javascript
复制
// 遍历 u 的非空子集
for (int s = u; s; s = (s - 1) & u)
{
  // s 是 u 的一个非空子集
}

成对变换

n

为奇数时,

n \oplus 1 = n - 1
n

为偶数时,

n \oplus 1 = n + 1

因此,“0与1”、“2与3”、“4与5”、... 关于

\oplus

是成对变换的

常用于 图论 中,无向图链式前向星 存图时,找 反向边

lowbit 运算

获得一个二进制表示数的最低位是 1 的位:

[ \mathrm{lowbit}(x) = x \& -x = x \& (\sim x + 1) ]

lowbit运算 可以找出整数二进制表示下的所有是 1 的位

lowbit运算 也是 树状数组 实现中的一个基本运算

代码语言:javascript
复制
int lowbit(int u)
{
    return u & -u;
}

习题

a^b

题目描述

a

b

次方对

p

取模的值。

输入格式

三个整数

a

,

b

,

p

,在同一行用空格隔开。

输出格式

输出一个整数,表示

a^b \bmod p

的值。

数据范围

0≤a,b≤10^9
1≤p≤10^9

输入样例

代码语言:javascript
复制
3 2 7

输出样例

代码语言:javascript
复制
2

解析

快速幂模板

时间复杂度:

O(\log_2 b)
代码语言:javascript
复制
int power(int a, int b, int p)
{
    int res = 1 % p;
    for (; b; b >>= 1)
    {
        if (b & 1) res = (long long) res * a % p;
        a = (long long) a * a % p;
    }
    return res;
}

64位整数乘法

题目描述

a

b

p

取模的值。

输入格式

第一行输入整数

a

,第二行输入整数

b

,第三行输入整数

p

输出格式

输出一个整数,表示

a * b \bmod p

的值。

数据范围

1≤a,b,p≤10^{18}

输入样例

代码语言:javascript
复制
3
4
5

输出样例

代码语言:javascript
复制
2

解析一

龟速乘模板

时间复杂度:

O(\log_2 b)
代码语言:javascript
复制
long long mul(long long a, long long b, long long p)
{
    long long res = 0;
    for (; b; b >>= 1)
    {
        if (b & 1) res = (res + a) % p;
        a = a * 2 % p;
    }
    return res;
}

解析二

公式:

a * b \bmod p = a * b - \lfloor{\dfrac{a * b}{p}}\rfloor * p

时间复杂度:

O(1)

虽然

a * b

\lfloor{\dfrac{a * b}{p}}\rfloor * p

都很大,但易知他们的差在

0

~

p-1

之间

因此,我们只需关注他们的低位即可

代码语言:javascript
复制
typedef unsigned long long ull;
ull mul(ull a, ull b, ull p) {
	a %= p, b %= p;  // 当a,b一定在0~p之间时,此行不必要
	ull c = (long double)a * b / p;
	ull x = a * b, y = c * p;
	long long ans = (long long)(x % p) - (long long)(y % p);
	if (ans < 0) ans += p;
	return ans;
}

最短Hamilton路径

题目描述

给定一张

n

个点的带权无向图,点从

0

n−1

标号,求起点

0

到终点

n−1

的最短 Hamilton 路径。

Hamilton 路径的定义是从

0

n−1

不重不漏地经过每个点恰好一次。

输入格式

第一行输入整数

n

接下来

n

行每行

n

个整数,其中第

i

行第

j

个整数表示点

i

j

的距离(记为

a[i,j]

)。

对于任意的

x

,

y

,

z

,数据保证

a[x,x]=0

a[x,y]=a[y,x]

并且

a[x,y]+a[y,z]≥a[x,z]

输出格式

输出一个整数,表示最短 Hamilton 路径的长度。

数据范围

1≤n≤20

0≤a[i,j]≤10^7

输入样例

代码语言:javascript
复制
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0

输出样例

代码语言:javascript
复制
18

解析

代码语言:javascript
复制
int f[1 << 20][20];
int hamilton(int n, int weight[20][20])
{
    memset(f, 0x3f, sizeof(f));
    f[1][0] = 0;
    for (int i = 1; i < 1 << n; i ++ )
        for (int j = 0; j < n; j ++ ) if (i >> j & 1)
            for (int k = 0; k < n; k ++ ) if (i >> k & 1)
                f[i][j] = min(f[i][j], f[i ^ 1 << j][k] + weight[k][j]);
    return f[(1 << n) - 1][n - 1];
}

起床困难综合症

题目描述

21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳。

作为一名青春阳光好少年,atm 一直坚持与起床困难综合症作斗争。

通过研究相关文献,他找到了该病的发病原因: 在深邃的太平洋海底中,出现了一条名为 drd 的巨龙,它掌握着睡眠之精髓,能随意延长大家的睡眠时间。

正是由于 drd 的活动,起床困难综合症愈演愈烈, 以惊人的速度在世界上传播。

为了彻底消灭这种病,atm 决定前往海底,消灭这条恶龙。

历经千辛万苦,atm 终于来到了 drd 所在的地方,准备与其展开艰苦卓绝的战斗。

drd 有着十分特殊的技能,他的防御战线能够使用一定的运算来改变他受到的伤害。

具体说来,drd 的防御战线由 n 扇防御门组成。

每扇防御门包括一个运算

op

和一个参数

t

,其中运算一定是

OR,XOR,AND

中的一种,参数则一定为非负整数。

如果还未通过防御门时攻击力为

x

,则其通过这扇防御门后攻击力将变为

x op t

最终 drd 受到的伤害为对方初始攻击力

x

依次经过所有

n

扇防御门后转变得到的攻击力。

由于 atm 水平有限,他的初始攻击力只能为

0

m

之间的一个整数(即他的初始攻击力只能在

0,1,…,m

中任选,但在通过防御门之后的攻击力不受

m

的限制)。

为了节省体力,他希望通过选择合适的初始攻击力使得他的攻击能让 drd 受到最大的伤害,请你帮他计算一下,他的一次攻击最多能使 drd 受到多少伤害。

输入格式

第 1 行包含 2 个整数,依次为

n,m

,表示 drd 有

n

扇防御门,atm 的初始攻击力为

0

m

之间的整数。

接下来

n

行,依次表示每一扇防御门。每行包括一个字符串

op

和一个非负整数

t

,两者由一个空格隔开,且

op

在前,

t

在后,

op

表示该防御门所对应的操作,

t

表示对应的参数。

输出格式

输出一个整数,表示 atm 的一次攻击最多使 drd 受到多少伤害。

数据范围

2 \le n \le 10^5

2 \le m \le 10^9

0 \le t \le 10^9
op \in \{OR, XOR, AND\}

输入样例

代码语言:javascript
复制
3 10
AND 5
OR 6
XOR 7

输出样例

代码语言:javascript
复制
1

样例解释

atm 可以选择的初始攻击力为

0,1,…,10

假设初始攻击力为

4

,最终攻击力经过了如下计算

代码语言:javascript
复制
4 AND 5 = 4

4 OR 6 = 6

6 XOR 7 = 1

类似的,我们可以计算出初始攻击力为

1,3,5,7,9

时最终攻击力为

0

,初始攻击力为

0,2,4,6,8,10

时最终攻击力为

1

,因此 atm 的一次攻击最多使 drd 受到的伤害值为

1

解析

代码语言:javascript
复制
int n, m, one = (1 << 30) - 1, non = 0, res = 0;
cin >> n >> m;
for (int i = 1; i <= n; i++ )
{
    string op; int t; cin >> op >> t;
    if (op == "AND") one &= t, non &= t;
    else if (op == "XOR") one ^= t, non ^= t;
    else one |= t, non |= t;
}
for (int i = 30; i >= 0; i -- )
{
    if (non >> i & 1) res += 1 << i;
    else if (one >> i & 1 && m >= 1 << i)
    {
        m -= 1 << i;
        res += 1 << i;
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-01-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 位运算基础概念
    • 运算符优先级
      • 二进制常用操作
      • 位运算的应用
        • 2 的幂次相关
          • 将一个数乘(除) 2 的非负整数次幂
          • 判断一个数是不是 2 的非负整数次幂
          • 对 2 的非负整数次幂取模
        • 模拟集合操作
          • 子集遍历
        • 成对变换
          • lowbit 运算
          • 习题
            • a^b
              • 题目描述
              • 解析
            • 64位整数乘法
              • 题目描述
              • 解析一
              • 解析二
            • 最短Hamilton路径
              • 题目描述
              • 解析
            • 起床困难综合症
              • 题目描述
              • 解析
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档