基础算法0x01

0x01 位运算

文章目录

  • 0x01 位运算
    • 前言
    • 0x01 位运算概述
    • 1.1 什么是bit?
    • 1.2 位运算符
    • 1.3 补码
    • 1.4 移位运算
    • 1.5 二进制状态压缩
    • 1.6 成对运算
    • 1.7 lowbit运算

前言

这是算法进阶指南的0x00 基本算法 的0x01章

0x00 基本算法主要学习的内容:

位运算、递推、二分、排序、倍增、贪心等算法

前缀和、差分、离散化等技巧

此次学习位运算

0x01 位运算概述

bit是信息的度量单位,bit取值0或1。程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算是对二进制的每一位对应做逻辑运算。

例如:6的二进制是110,11的二进制是1011,可=可以在110前补一位0,所以6的二进制是0110。这样好来理解一点!

​ 0110

AND 1011

---------------

0010 --> 2

此类运算还有或,非,异或等。

从右搭到左,最右边是最地位0,依次类推,最左边为最高位m-1。

1.1 什么是bit?

位 bit,来自英文bit,音译为“比特”,表示二进制位。

http://t.csdn.cn/JtmlL

1.2 位运算符

(328条消息) 位运算符详解_-白山茶的博客-CSDN博客_位运算符](https://blog.csdn.net/weixin_52140401/article/details/123856311)

这位大佬写的非常详细!

1.3 补码

我们在学习c++的时候,接触到了int,unsigned int两个整数类型,分别是有符号类型和无符号类型。

(1)以下以32位类型来学习他们两。

首先n位是什么?n位是由n个二进制数位组成的。

打开电脑自带的计算器来看看,bin二进制位输入32个1,

通过排列组合的知识可以知道,n位二进制数可以表示 $2^ n $ 个数。也就是说在0-4294967295里,32位二进制数可以提高0和1的组合来表示一个数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h7mQwXrL-1664294884187)(./image/jsjejz.png)]

也就对应着,用一个32位二进制数来表示的整数范围是:0-4294967295,但是实际真的是这样吗?在我们学的数学里有正整数,负整数和零。所以我们应该如何来表示负数呢?

聪明的读者是不是已经给出了答案:最高位设置为符号位,哪么如果是1,就为-,是0就为+。这样它也可以保存4294967295个数,不过这里是-2147483648 ~ 2147483647。可以利用计算器再来试试。

综上就引出了最高位是符号位,则是有符号整数int,不是符号位则是无符号数unsigned int。看实际情况,如果所有情况都是正的,就用unsigned int,这明显要比int类型表示的范围大。

提一个问题:那现在那么多数据超过表示的范围怎么办?

思考一下:

我们c++是不是有long,和long long类型。long至少32位,long long 至少64位。

那想一想要是我就用int来存放这个超过他表示范围的数呢?

(2)溢出情况:

unsigned int:

​ 1111 1111 1111 1111 1111 1111 1111 1111

运算 + 1

————————————————————————

~~(1)~~0000 0000 0000 0000 0000 000 0000 0000

哪么加二进制的111呢?

​ 1111 1111 1111 1111 1111 1111 1111 1111

运算 + 111

————————————————————————

(1) 0000 0000 0000 0000 0000 000 0000 0110

是不是就是32位无符号数自动对2^32取模。

int:

​ 0111 1111 1111 1111 1111 1111 1111 1111

运算 + 1

————————————————————————

​ 1000 0000 0000 0000 0000 000 0000 0000

为-2147483648 怎么变成负数了,有的同学就问了:1000 0000 0000 0000 0000 000 0000 0000 为什么是-2147483648啊?教练我不懂!

答:

基础算法0x01_第1张图片

为什么0xffffffff是-1?(计算机对整型的存储) - 知乎 (zhihu.com)

​ 1111 1111 1111 1111 1111 1111 1111 1111

运算 + 1

————————————————————————

~~(1)~~0000 0000 0000 0000 0000 000 0000 0000

为 0

补码比反码的优势:

  • 自然溢出取模
  • 解决了0的编码唯一性

(3)十六进制

二进制表示一个int需要32位,比较繁琐。十进制又看不出二进制的细节,所有常用16进制来表示。

为什么是16进制?
2 和 16有什么关系

2^4 = 16 也就是 (1111)2 = 16,四个二进制位= 一个十六进制位。

(328条消息) 十六进制表示_计算机为什么用二进制和十六进制_long_far的博客-CSDN博客_计算机是16进制还是二进制

(4)一个重要的数:
0x7f 7f 7f 7f -->2147483647

0x 3f 3f 3f 3f -->1061109506

0x 3f 3f 3f 3f的好处

  • 整数的两倍不超过0x7f 7f 7f 7f
  • 每八位都是相同的

(5)memset(a,val, sizeof(a))函数

初始化一个int数组a,把数值val(0x00~0xff)填充到数组a的每一个字节上。

解读:

一个int 32个二进制位。

一个字节8个二进制位。

四个字节一个int,但是他是按一个字节一个字节来赋值的,所以可以把每一个int,赋值为每八位二进制位相同的一个int类型。

作用:memset(a,0x3f, sizeof(a)),给数组a赋值0x3f3f3f3f,把一个数组赋值为正无穷,避免溢出来判断。

1.4 移位运算

(1)左移

二进制表示下,把数字同时向左移动,低位以0填充,高位越界后舍弃。

(2)算术右移

二进制表示下,把数字同时向右移动,低位越界舍弃,高位符号位填充。

(3)逻辑右移

二进制表示下,把数字同时向右移动,低位越界舍弃,高位0填充。

例题:

1、求a的b次方对p取模的值

建议大家先自己思考来做:

基础算法0x01_第2张图片

代码:

#include
using namespace std;



int power(int a, int b, int p){
	int ans = 1 % p; //这里取模是为了处理模为0的情况
	while(b){
		if(b & 1){
			ans = (long long)ans * a % p;//对每一个二进制位进行处理
		}
		a = (long long)a * a % p;//累乘
		b >>= 1;
	}
	return ans;
}


int main()
{
	int a,b,p,res;
	cin >> a >> b >> p;
	res = power(a,b,p);
	cout << res << endl;
	
	return 0;
}

解析:(328条消息) 求 a 的 b 次方对 p 取模的值_weixin_30371469的博客-CSDN博客

2、64位整数乘法

代码:

#include 
using namespace std;

int main(){
    long long a, b, p;
    cin >> a >> b >> p;
    long long ans = 0;
    for(; b; b >>= 1){
        if(b & 1){
            ans = (ans + a) % p;
        }
        a = a * 2 % p;
    }
    cout << ans << endl;
    return 0;
}

解析:(328条消息) 64位整数乘法_SJLX的博客-CSDN博客_64位乘法

1.5 二进制状态压缩

描述:二进制状态压缩,是指将一个长度为 m 的 bool 数组用一个 m 位二进制整数表示并存储的方法。

书中的几个操作:

//取出整数 n 在二进制表示下的第 k 位
(n >> k) & 1;

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

//把整数 n 在二进制表示下的第 k 位 取反
n ^ (1 << k)

//对整数 n 在二进制表示下的第 k 位赋值 1
n | (1 << k)

//对整数 n 在二进制表示下的第 k 位赋值 0
n & (~(1 << k))

方法运算简单,节省运行的时间和空间。

例题:

最短Hamilton路径

给定一张 nn 个点的带权无向图,点从 0~n-1 标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。

输入格式

第一行输入整数n。

接下来n行每行n个整数,其中第ii行第jj个整数表示点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

输入样例:

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

输出样例:

18

代码:

#include 
#include 

using namespace std;

const int N = 20, M = 1 << N;

int n;
int w[N][N];
int f[M][N];

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            cin >> w[i][j];
    
    memset(f, 0x3f, sizeof f);
    f[1][0] = 0;
    for (int i = 0; i < 1 << n; i ++ )
        for (int j = 0; j < n; j ++ )
        {
            if (i >> j & 1)
            {
                for (int k = 0; k < n; k ++ )
                    if ((i - (1 << j)) >> k & 1)
                    {
                        f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);
                    }
            }
        }
    
    cout << f[(1 << n) - 1][n - 1] << endl;
    return 0;
}

解答:最短哈密顿路径 - 简书 (jianshu.com)

ps:y总是真的厉害,acwing。

1.6 成对运算

对于非负整数:

n为偶数时,n xor 1 等于 n + 1

n为奇数时,n xor 1 等于 n - 1

(图论中邻接表中边集的储存。在具有无向边(双向边)的图中把一对正反方向的边分别储存在邻接表数组的第n和第n+1位置(其中n为偶数),就可以通过xor 1 的运算获得与当前边(x,y)反向的边(y,x)的储存位置。详细请看邻接表。

1.7 lowbit运算

(1)描述

任意一个整数可被表达成二进制如: (6)10 =(110)2

那么定义一个函数f=lowbit(x),表示这个数的二进制表示中最低位的1所对应的值,lowbit(6)=2

也就是,lowbit(n)函数取出n在二进制表示下最低位的1以及它后面的0构成的数值。

(2)实现方式(100100)2 也就是(36)10为例子

1、假设 x 有n个二进制位,且最低位的 ‘1’ 在第 k 位上,所以0~k-1位都是零。

5 4 3 k=2 1 0
1 0 0 1 0 0

符合的。

2、则按位取反 (~ x) 的二进制的第 k 位为0, 0~k-1位全部为1(k+1 ~ n) 位也被取反。

5 4 3 k=2 1 0
0 1 1 0 1 1

3、所以于是 (~ x+ 1) 由于进位,(0 ~ k-1) 位全部为0,第k位为1, (k+1 ~ n) 位仍然和原来相反

也就是011011 + 1 = 011100

5 4 3 k=2 1 0
0 1 1 1 0 0

4、那么n&(~ n + 1)自然就只剩下最低位的1以及它后面的0构成的数值了

5 4 3 k=2 1 0
0 1 1 1 0 0
1 0 0 1 0 0
0 0 0 1 0 0

看图表确实如此。

由于补码下:

~ x=-1-x

所以有

lowbit(x) = x & (-x)

基础算法0x01_第3张图片

你的关注和鼓励是我不断更新的动力!谢谢~

你可能感兴趣的:(算法萌新的自我修养)