这是算法进阶指南的0x00 基本算法 的0x01章
0x00 基本算法主要学习的内容:
位运算、递推、二分、排序、倍增、贪心等算法
前缀和、差分、离散化等技巧
此次学习位运算
bit是信息的度量单位,bit取值0或1。程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算是对二进制的每一位对应做逻辑运算。
例如:6的二进制是110,11的二进制是1011,可=可以在110前补一位0,所以6的二进制是0110。这样好来理解一点!
0110
AND 1011
---------------
0010 --> 2
此类运算还有或,非,异或等。
从右搭到左,最右边是最地位0,依次类推,最左边为最高位m-1。
位 bit,来自英文bit,音译为“比特”,表示二进制位。
http://t.csdn.cn/JtmlL
(328条消息) 位运算符详解_-白山茶的博客-CSDN博客_位运算符](https://blog.csdn.net/weixin_52140401/article/details/123856311)
这位大佬写的非常详细!
我们在学习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啊?教练我不懂!
答:
为什么0xffffffff是-1?(计算机对整型的存储) - 知乎 (zhihu.com)
那
1111 1111 1111 1111 1111 1111 1111 1111
运算 + 1
————————————————————————
~~(1)~~0000 0000 0000 0000 0000 000 0000 0000
为 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的好处
(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)左移
二进制表示下,把数字同时向左移动,低位以0填充,高位越界后舍弃。
(2)算术右移
二进制表示下,把数字同时向右移动,低位越界舍弃,高位符号位填充。
(3)逻辑右移
二进制表示下,把数字同时向右移动,低位越界舍弃,高位0填充。
例题:
1、求a的b次方对p取模的值
建议大家先自己思考来做:
代码:
#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位乘法
描述:二进制状态压缩,是指将一个长度为 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。
对于非负整数:
n为偶数时,n xor 1 等于 n + 1
n为奇数时,n xor 1 等于 n - 1
(图论中邻接表中边集的储存。在具有无向边(双向边)的图中把一对正反方向的边分别储存在邻接表数组的第n和第n+1位置(其中n为偶数),就可以通过xor 1 的运算获得与当前边(x,y)反向的边(y,x)的储存位置。详细请看邻接表。
(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)
你的关注和鼓励是我不断更新的动力!谢谢~