0x01
位运算在c++中,8位二进制数代表char类型,范围为-128~127
,其中0xFF
代表-1,0x7F
代表最大值127。
另外,m位的二进制数,为方便起见,最低位称为第0位,从右往左依次类推,最高位为m-1位。
32位无符号整数 unsigned int:
直接把这32位编码C
看做32位二进制数N
。
32位有符号整数 int:
以最高为符号位,0表示非负数,1表示负数。
对于最高位为0的每种编码C
,直接看做32位二进制数S
。
同时,定义该编码按位取反后得到的编码~C
的数值为-1-S
。
运算发生算术溢出时,32位无符号整数相当于自动对2^32取模。有符号整数算术溢出时可能会出现负数。
补码也被称作”二补数“。还有一种编码称作反码,也叫”一补数“,直接把C的每一位取反表示负C。补码和反码在负数表示中,绝对值相差1。
32位补码 | int(十进制) | int(十六进制) |
---|---|---|
0000...0000 |
0 |
0x0 |
0111...1111 |
21 4748 3647 |
0x7F FF FF FF |
00111111重复四次 |
10 6110 9567 |
0x3F 3F 3F 3F |
1111...1111 |
-1 |
0xFF FF FF FF |
上述表中的0x3F 3F 3F 3F
是一个非常有用的数值,它是满足以下两个条件的最大整数。
1.整数的两倍不超过0x7F FF FF FF
,即int表示的最大整数。
2.整数的每8位(每个字节)都是相同的。
我们常用memset(a,val,sizeof(a))
来初始化一个int数组a,该语句把数值val(0x00~0xFF)
填充到数组a的每个字节上,而一个int占用4的字节,所以memset
只能赋值出“每8位都相同”的int。为了避免算术上溢,我们通常用memset(a,0x3f,sizeof(a))
给数组赋值0x3F 3F 3F 3F
。
一般设置无穷大为0x3F 3F 3F 3F
,设置无穷小为0xcF cF cF cF
。
左移
在二进制表示下把数字同时往左移动,低位以0填充,高位越界后舍弃。
1 < < n = 2 n , n < < 1 = 2 n 1<
右移
这里的右移特指算术右移。
在二进制补码表示下把数字同时向右移动,高位以符号位填充,低位越界后舍弃。
n > > 1 = ⌊ n 2.0 ⌋ n>>1=\lfloor \frac{n}{2.0} \rfloor n>>1=⌊2.0n⌋
算术右移等于除以2向下取整,(-3)>>1=-2
,3>>1=1
。
而(-3)/2=-1
,3/2=1
。除法和算术右移有区别,注意区分。
快速幂
计算 a b m o d p 计算a^b mod p 计算abmodp
long long quickpow(long long a,long long b,long long p)
{
a%=p;
long long ans=1;
while(b)
{
if(b&1) ans=ans*a%p;
a=a*a%p;
b>>=1;
}
return ans%p;
}
时间复杂度O(logb)
a
和b
不能超过long long
范围9e18
二进制状态压缩是指一个长度为m的bool
数组可以用一个m位二进制数来表示并储存的方法。利用下列位运算的方法可以实现对原bool
数组中对应下标元素的存取。
操作 | 运算 |
---|---|
取出整数n 在二进制表示下的第k 位 |
(n>>k)&1 |
取出整数n 在二进制表示下的第0~k-1 位(后k位) |
n&((1< |
把整数n 在二进制下的第k 位取反 |
n^(1< |
对整数n 在二进制表示下的第k 位赋值1 |
n|(1< |
对整数n 在二进制表示下的第k 位赋值0 |
n&(~(1< |
这种方法运算简便,并且节省了程序运行时间和空间。当m不太大的时候,可以直接使用一个整数类型来储存。当m较大时,可以使用若干个整数类型(int数组)来储存,也可以直接利用C++STL
提供的bitset
功能实现(第0x71
节)。
通过计算可以发现,对于非负整数n:
n为偶数时,n xor 1
等于n+1
。
n为奇数时,n xor 1
等于n-1
。
因此,“0和1”,“2和3”,“4和5”…关于xor 1
运算构成成对变换。
应用:
常用于图论邻接表中边集的储存。在具有无向边(双向边)的图中把一对正反方向的边分别储存在邻接表数组的第n和n+1的位置(其中n为偶数),就可以通过xor 1
的运算获得与当前边(x,y)
反向的边(y,x)
的储存位置。详细在邻接表(第0x13
节)。
lowbit
运算lowbit(n)
定义为非负整数n在二进制表示下“最低位的1及后边所有的0”构成的数值。
lowbit(n) = n&(~n+1) = n&(-n)
lowbit
运算配合Hash
可以找出整数二进制下所有是1的位,所花费的时间与1的个数同级。
为了到达这一目的可以不断个给n
赋值为n-lowbit(n)
,直到n=0
。例如n=9=1001。lowbit(9)=1
,9-lowbit(9)=8=1000
,lowbit(8)=8
,8-lowbit(8)=0
。取1和8的对数 l o g 2 1 log_2 1 log21和 l o g 2 8 log_28 log28,可知是n的第0位和第3位。但c++的log函数以e为底,且时间复杂度大,所以我们预处理一个数组,通过Hash来代替log运算。
当n较小时,最简单的方法是建立一个数组H,令H[ 2 k 2^k 2k]=k
const int MAX_N = 1 << 20;
int H[MAX_N + 1];
for (int i = 0; i <= 20; ++i)
H[1 << i] = i;
while (cin >> n)
{
while (n)
{
cout << H[n & -n] << ' ';
n -= n & -n;
}
cout << endl;
}
稍微复杂但效率更高的方法是建立一个长度为37的数组H,令H[ 2 k m o d 37 2^kmod37 2kmod37]=k。
这里利用了一个小技巧: ∀ k ∈ [ 0 , 35 ] \forall k\in[0,35] ∀k∈[0,35], 2 k m o d 37 2^kmod37 2kmod37互不相等,且恰好取遍整数1~36。修改后的程序:
int H[37];
for (int i = 0; i < 36; ++i)
H[(1ll << i) % 37] = i;
while (cin >> n)
{
while (n)
{
cout << H[(n & -n) % 37] << ' ';
n -= n & -n;
}
cout << endl;
}