位运算
一、原码反码补码
-
原码:有符号整数 最高位1 0表示正负
反码(一补数):正数同原码 负数符号位不变 其余位取反
补码(二补数):正数同原码(S) 负数符号位不变 其余位取反 +1 (-1-(~S))
-
补码:自然溢出取,解决0唯一性问题
-
0x3F 3F 3F 3F (1 061 109 567) :
- 整数的两倍不超过0x7F FF FF FF(int 能表示最大整数)
- 整数的每八位都是相同的
//memset(a,val,sizeof(a)); // val: 0x00~0xFF 把val填充到a的每个字节上,一个int占用4个字节 // memset 只能赋值每八位都相同的int //把一个数组的数值初始化正无穷 memset(a,0x3f,sizeof(a));
二、 移位运算
-
左移:低位用0填充 高位越界舍弃 \(1<<n = 2^n\)
右移:实现方式由编译器决定,通常使用算术右移
算术右移:高位符号位填充 低位越界舍弃 (除以2向下取整)
逻辑右移:高位0填充 低位越界舍弃
-
求 \(a^b\) mod p ,\(1<=a,b,p<=10^9\)
分析:
快速幂: \(O(log_2{b})\)
$b = c_{k-1}2^{k-1} + c_{k-2}2^{k-2} +···+c_{0}2^{0} $
\(a^b =a^{ c_{k-1}*2^{k-1} }* a^{c_{k-2}*2^{k-2}} *···*a^{c_{0}*2^{0}}\)
b => \(c_k\) , a=> \(a^{2^k}\)
int power(int a,int b,int p){
int ans = 1%p;
for(;b;b>>=1){
if(b&1) s = (long long)ans*a%p;
a = (long long)a*a%p;
}
return ans;
}
// 两个数值执行运算,以最高数值类型为标准
// 两个int相乘,CPU只提供一个32位寄存器保存结果 容易造成越界
// 将ans转换为long long ,赋值隐式转换为int存入ans
-
64位整数乘法
求a乘以b对p取模的值,其中 \(1<=a,b,p<=10^{18}\)
分析:
方法1:类似快速幂思想 \(2*10^{18} <10^{19}\) 没越界 \(O(log_2{b})\)
$b = c_{k-1}2^{k-1} + c_{k-2}2^{k-2} +···+c_{0}2^{0} $
\(a*b = a*c_{k-1}*2^{k-1} + a*c_{k-2}*2^{k-2} +···+a*c_{0}*2^{0}\)
long long mul(long long a,long long b,long long p){
long long ans = 0;
for(;b;b>>=1){
if(b&1) ans = (ans+a)%p;
a = a*2%p;
}
return ans;
}
方法2:\(a*b \% p = a*b - \lfloor a*b/p \rfloor *p\)
\(a*b\) 和 \(\lfloor a*b/p \rfloor *p\) 可能很大,但两者的差一定在 0~p-1之间 所以只用关注低位 运算溢出不影响
long long mul(long long a,long long b, long long p){
a%=p; b%=p;
long long c = (long double)a*b/p;
long long ans = a*b - c*p;
if(ans < 0) ans+=p;
else if(ans >= p) ans-=p;
return ans;
}
三、 二进制状态压缩
-
二进制状态压缩: 将一个长度为m的bool数组 用 一个m位二进制整数表示并存储 的方法
m 不太大 直接用一个整数类型存储
m较大 用若干个整数类型(int 数组)
使用C++ STL 提供的 bitset 实现
操作 | 运算 |
---|---|
取出整数n在二进制表示下的第k位 | (n>>k)&1 |
取出整数n在二进制表示下的第0~k-1位(后k位) | n&((1<<k)-1) |
把整数n在二进制表示下的第k位取反 | x xor (1<<k) |
对整数n在二进制表示下的第k位赋值1 | n|(1<<k) |
对整数n在二进制表示下的第k位赋值0 | n&(~(1<<k)) |
- 最短Hamilton路径(哈密顿)
给定一张n(n<=20)个点的带权无向图,点从0~n-1标号,求起点0到终点n-1的最短Hamilton路径。
Hamilton路径: 从0到n-1不重不漏的经过每个点恰好1次
分析:
方法1:朴素算法 枚举全排列 求路径长度最小值 \(O(n*n!)\)
方法2:二进制转态压缩DP \(O(n^2 * 2^n)\)
F[i,j] i表示状态(一个n位二进制数表示每个点被经过的状态 第i位二进制表示点i状态) j表示处于点j
F[i,j]表示 点被经过状态且处于点j时 的最短路径
\(\begin{equation} \left\{ \begin{array}{lr} F[1,0]=0 & \\ F[i,j] = min{F[i \quad xor \quad(1<<j), \quad k]+weight(k,j)} \qquad 0 <= k<n , (i>>k)=1& \end{array} \right.\end{equation}\)
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];
}
四、成对变换
-
对于非负整数n
$$x \quad xor \quad 1=
\begin{cases}
n+1& \text{n为偶数}\
n-1& \text{n为奇数}
\end{cases}$$
所以 "0 与 1", "2 与 3", "3 与 4" 关于xor 1构成成对变换
- 应用
- 图论邻接表中边集的存储: 在无向边或双向边的图中,把正反方向的边放在数组n 和 n+1位置(n为偶数) 可以通过xor 1 获取当前边的反向边的存储位置
五、lowbit运算
-
lowbit(n):取出非负整数n在二进制表示下最低位1以及它后面的0构成的数值
lowbit(n) = n & (~ n +1) = n & (-n)
-
应用
- Hash :可以找出整数二进制表示下所有是1的位,所花费的时间与1的个数同级
- 树状数组