- 「数学与数论详解·一」进制
0. 前言
0-1. 说明
本系列「数学与数论详解」正式开坑。
如果你觉得对你有用,那就订阅 + 关注一下吧!
本文共 12876 12876 12876 字。
0-2. 目录
1. 整数进制转换
1-1. 十进制
在我们日常生活中,通常用的都是十进制,遵循满十进一的原则。
比如
114514
114514
114514 这个数,我们现在为了表示它是十进制下的数,我们写成
(
114514
)
10
(114514)_{10}
(114514)10。
如果我们将
(
114514
)
10
(114514)_{10}
(114514)10 变为
10
10
10 的幂次的和,便是
(
114514
)
10
=
1
×
1
0
5
+
1
×
1
0
4
+
4
×
1
0
3
+
5
×
1
0
2
+
1
×
1
0
1
+
4
×
1
0
0
(114514)_{10}=1\times10^5+1\times10^4+4\times10^3+5\times10^2+1\times10^1+4\times10^0
(114514)10=1×105+1×104+4×103+5×102+1×101+4×100。
1-2. 二进制
我们知道,在计算机中,只有
0
0
0 和
1
1
1。用这一规则表示的数,是二进制下的数,遵循满二进一的原则。
比如
11001100
11001100
11001100 这个数,我们现在为了表示它是十进制下的数,我们写成
(
11001100
)
2
(11001100)_2
(11001100)2。
如果我们将
(
11001100
)
2
(11001100)_2
(11001100)2 变为十进制下
2
2
2 的幂次的和,便是
(
11001100
)
2
=
1
×
2
7
+
1
×
2
6
+
0
×
2
5
+
0
×
2
4
+
1
×
2
3
+
1
×
2
2
+
0
×
2
1
+
0
×
2
0
(11001100)_2=1\times2^7+1\times2^6+0\times2^5+0\times2^4+1\times2^3+1\times2^2+0\times2^1+0\times2^0
(11001100)2=1×27+1×26+0×25+0×24+1×23+1×22+0×21+0×20。
1-3. 二进制和十进制之间的转换
于是我们经常会将二进制和十进制之间进行转换。
1-3-1. 二进制转十进制
以上文的
(
11001100
)
2
(11001100)_2
(11001100)2 为例。
上文我们将
(
11001100
)
2
(11001100)_2
(11001100)2 变为十进制下
2
2
2 的幂次的和。我们只要把其算出来即可。
(
11001100
)
2
=
1
×
2
7
+
1
×
2
6
+
0
×
2
5
+
0
×
2
4
+
1
×
2
3
+
1
×
2
2
+
0
×
2
1
+
0
×
2
0
=
1
×
128
+
1
×
64
+
0
×
32
+
0
×
16
+
1
×
8
+
1
×
4
+
0
×
2
+
0
×
1
=
128
+
64
+
8
+
4
=
204.
\begin{aligned} &(11001100)_2\\ =&1\times2^7+1\times2^6+0\times2^5+0\times2^4+1\times2^3+1\times2^2+0\times2^1+0\times2^0\\ =&1\times128+1\times64+0\times32+0\times16+1\times8+1\times4+0\times2+0\times1\\ =&128+64+8+4\\ =&204. \end{aligned}
====(11001100)21×27+1×26+0×25+0×24+1×23+1×22+0×21+0×201×128+1×64+0×32+0×16+1×8+1×4+0×2+0×1128+64+8+4204.
我们为了方便,可以先预处理出
2
k
2^k
2k 的值然后计算。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int ans=0,a[35];
string s;
cin>>s;
a[0]=1;
for(int i=1;i<=30;i++) a[i]=a[i-1]*2;//预处理 2 的幂次
for(int i=0;i<s.size();i++) ans+=(s[i]-48)*a[s.size()-i-1];//累加
cout<<ans;
return 0;
}
当然,你也可以用秦九韶算法计算。
(
11001100
)
2
=
1
×
2
7
+
1
×
2
6
+
0
×
2
5
+
0
×
2
4
+
1
×
2
3
+
1
×
2
2
+
0
×
2
1
+
0
×
2
0
=
(
1
×
2
6
+
1
×
2
5
+
0
×
2
4
+
0
×
2
3
+
1
×
2
2
+
1
×
2
1
+
0
×
2
0
)
×
2
+
0
×
2
0
=
(
(
1
×
2
5
+
1
×
2
4
+
0
×
2
3
+
0
×
2
2
+
1
×
2
1
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
=
(
(
(
1
×
2
4
+
1
×
2
3
+
0
×
2
2
+
0
×
2
1
+
1
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
=
(
(
(
(
1
×
2
3
+
1
×
2
2
+
0
×
2
1
+
0
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
=
(
(
(
(
(
1
×
2
2
+
1
×
2
1
+
0
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
=
(
(
(
(
(
(
1
×
2
1
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
=
(
(
(
(
(
(
(
1
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
=
(
(
(
(
(
(
1
×
2
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
=
(
(
(
(
(
3
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
=
(
(
(
(
6
×
2
+
0
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
=
(
(
(
12
×
2
+
1
×
2
0
)
×
2
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
=
(
(
25
×
2
+
1
×
2
0
)
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
=
(
51
×
2
+
0
×
2
0
)
×
2
+
0
×
2
0
=
102
×
2
+
0
×
2
0
=
204.
\begin{aligned} &(11001100)_2\\ =&1\times2^7+1\times2^6+0\times2^5+0\times2^4+1\times2^3+1\times2^2+0\times2^1+0\times2^0\\ =&(1\times2^6+1\times2^5+0\times2^4+0\times2^3+1\times2^2+1\times2^1+0\times2^0)\times2+0\times2^0\\ =&((1\times2^5+1\times2^4+0\times2^3+0\times2^2+1\times2^1+1\times2^0)\times2+0\times2^0)\times2+0\times2^0\\ =&(((1\times2^4+1\times2^3+0\times2^2+0\times2^1+1\times2^0)\times2+1\times2^0)\times2+0\times2^0)\times2+0\times2^0\\ =&((((1\times2^3+1\times2^2+0\times2^1+0\times2^0)\times2+1\times2^0)\times2+1\times2^0)\times2+0\times2^0)\times2+0\times2^0\\ =&(((((1\times2^2+1\times2^1+0\times2^0)\times2+0\times2^0)\times2+1\times2^0)\times2+1\times2^0)\times2+0\times2^0)\times2+0\times2^0\\ =&((((((1\times2^1+1\times2^0)\times2+0\times2^0)\times2+0\times2^0)\times2+1\times2^0)\times2+1\times2^0)\times2+0\times2^0)\times2+0\times2^0\\ =&(((((((1\times2^0)\times2+1\times2^0)\times2+0\times2^0)\times2+0\times2^0)\times2+1\times2^0)\times2+1\times2^0)\times2+0\times2^0)\times2+0\times2^0\\ =&((((((1\times2+1\times2^0)\times2+0\times2^0)\times2+0\times2^0)\times2+1\times2^0)\times2+1\times2^0)\times2+0\times2^0)\times2+0\times2^0\\ =&(((((3\times2+0\times2^0)\times2+0\times2^0)\times2+1\times2^0)\times2+1\times2^0)\times2+0\times2^0)\times2+0\times2^0\\ =&((((6\times2+0\times2^0)\times2+1\times2^0)\times2+1\times2^0)\times2+0\times2^0)\times2+0\times2^0\\ =&(((12\times2+1\times2^0)\times2+1\times2^0)\times2+0\times2^0)\times2+0\times2^0\\ =&((25\times2+1\times2^0)\times2+0\times2^0)\times2+0\times2^0\\ =&(51\times2+0\times2^0)\times2+0\times2^0\\ =&102\times2+0\times2^0\\ =&204. \end{aligned}
================(11001100)21×27+1×26+0×25+0×24+1×23+1×22+0×21+0×20(1×26+1×25+0×24+0×23+1×22+1×21+0×20)×2+0×20((1×25+1×24+0×23+0×22+1×21+1×20)×2+0×20)×2+0×20(((1×24+1×23+0×22+0×21+1×20)×2+1×20)×2+0×20)×2+0×20((((1×23+1×22+0×21+0×20)×2+1×20)×2+1×20)×2+0×20)×2+0×20(((((1×22+1×21+0×20)×2+0×20)×2+1×20)×2+1×20)×2+0×20)×2+0×20((((((1×21+1×20)×2+0×20)×2+0×20)×2+1×20)×2+1×20)×2+0×20)×2+0×20(((((((1×20)×2+1×20)×2+0×20)×2+0×20)×2+1×20)×2+1×20)×2+0×20)×2+0×20((((((1×2+1×20)×2+0×20)×2+0×20)×2+1×20)×2+1×20)×2+0×20)×2+0×20(((((3×2+0×20)×2+0×20)×2+1×20)×2+1×20)×2+0×20)×2+0×20((((6×2+0×20)×2+1×20)×2+1×20)×2+0×20)×2+0×20(((12×2+1×20)×2+1×20)×2+0×20)×2+0×20((25×2+1×20)×2+0×20)×2+0×20(51×2+0×20)×2+0×20102×2+0×20204.
#include<bits/stdc++.h>
using namespace std;
int main()
{
int ans=0;
string s;
cin>>s;
for(int i=0;i<s.size();i++) ans=ans*2+(s[i]-48);//秦九韶算法
cout<<ans;
return 0;
}
1-3-2. 十进制转二进制
以上文的
(
114514
)
10
(114514)_{10}
(114514)10 为例。
容易想到,我们只要表示成
2
2
2 的幂次的和即可。
我们考虑倒推秦九韶算法。
我们知道
2
1
,
2
2
,
2
3
,
⋯
2^1,2^2,2^3,\cdots
21,22,23,⋯ 一定是
2
1
2^1
21 的整数倍数。因此,对于
2
0
2^0
20,我们只要计算
114514
m
o
d
2
114514\bmod2
114514mod21 即可。
然后将
114514
114514
114514 变为
⌊
114514
2
⌋
=
57257
\lfloor\dfrac{114514}{2}\rfloor=57257
⌊2114514⌋=57257,继续计算,直到为
0
0
0。
我们类似短除法地写出来:
2
∣
114514
‾
.
.
.
.
.
.
.
.
.
.
0
2
∣
57257
‾
.
.
.
.
.
.
.
.
.
.
1
2
∣
28628
‾
.
.
.
.
.
.
.
.
.
.
0
2
∣
14314
‾
.
.
.
.
.
.
.
.
.
.
0
2
∣
7157
‾
.
.
.
.
.
.
.
.
.
.
1
2
∣
3578
‾
.
.
.
.
.
.
.
.
.
.
0
2
∣
1789
‾
.
.
.
.
.
.
.
.
.
.
1
2
∣
894
‾
.
.
.
.
.
.
.
.
.
.
0
2
∣
447
‾
.
.
.
.
.
.
.
.
.
.
1
2
∣
223
‾
.
.
.
.
.
.
.
.
.
.
1
2
∣
111
‾
.
.
.
.
.
.
.
.
.
.
1
2
∣
55
‾
.
.
.
.
.
.
.
.
.
.
1
2
∣
27
‾
.
.
.
.
.
.
.
.
.
.
1
2
∣
13
‾
.
.
.
.
.
.
.
.
.
.
1
2
∣
6
‾
.
.
.
.
.
.
.
.
.
.
0
2
∣
3
‾
.
.
.
.
.
.
.
.
.
.
1
2
∣
1
‾
.
.
.
.
.
.
.
.
.
.
1
0
\def\ul{\underline} \begin{aligned} 2\ul{|114514}&..........0\\ 2\ul{|57257}&..........1\\ 2\ul{|28628}&..........0\\ 2\ul{|14314}&..........0\\ 2\ul{|7157}&..........1\\ 2\ul{|3578}&..........0\\ 2\ul{|1789}&..........1\\ 2\ul{|894}&..........0\\ 2\ul{|447}&..........1\\ 2\ul{|223}&..........1\\ 2\ul{|111}&..........1\\ 2\ul{|55}&..........1\\ 2\ul{|27}&..........1\\ 2\ul{|13}&..........1\\ 2\ul{|6}&..........0\\ 2\ul{|3}&..........1\\ 2\ul{|1}&..........1\\ 0\\ \end{aligned}
2∣1145142∣572572∣286282∣143142∣71572∣35782∣17892∣8942∣4472∣2232∣1112∣552∣272∣132∣62∣32∣10..........0..........1..........0..........0..........1..........0..........1..........0..........1..........1..........1..........1..........1..........1..........0..........1..........1
右侧的是分别算下来的余数,因为我们算的末位,所以要将余数从下往上依次连起来,这便是这个数字的二进制。
(
114514
)
10
=
(
11011111101010010
)
2
(114514)_{10}=(11011111101010010)_2
(114514)10=(11011111101010010)2
递归写法:
#include<bits/stdc++.h>
using namespace std;
void f(int x)
{
if(!x) return;
f(x/2);
cout<<x%2;//倒序输出
}
int main()
{
int n;
cin>>n;
if(!n) puts("0");
else f(n);
return 0;
}
循环写法:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin>>n;
string s="";
while(n)
{
s=(char)(n%2+48)+s;//注意连接顺序
n/=2;
}
cout<<(s==""?"0":s);
return 0;
}
其余进制也是一样的。
1-4. 例题
1-4-1. Luogu P2084 进制转换
用于理解进制转换的过程,直接模拟即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int a;
bool f=0;
string b;
cin>>a>>b;
for(int i=0;i<b.size();i++)
{
if(b[i]!='0')//注意要不为零
{
if(f) cout<<'+';//处理加号
cout<<b[i]<<'*'<<a<<'^'<<b.size()-i-1;//直接模拟输出
f=1;
}
}
return 0;
}
1-4-2. Luogu P1143 进制转换
考虑将
n
n
n 进制数先转换为
10
10
10 进制,然后再转为
m
m
m 进制。
对于
>
10
>10
>10 的进制,由于要字母,我们可以用一个常量字符串
R
R
R 来储存。访问数码时直接写
R
i
R_i
Ri 即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
const string R="0123456789ABCDEF";
int m;
void f(int n)
{
if(!n) return;
f(n/m);
cout<<R[n%m];//倒序输出
}
int main()
{
int n,p=0;
string s;
cin>>n>>s>>m;
for(int i=0;i<s.size();i++) p=p*n+R.find(s[i]);//转十进制
f(p);
return 0;
}
*2. 实数进制转换
实数进制转换很少用到,读者自行选读。
2-1. 十进制
如 ( 114.514 ) 10 (114.514)_{10} (114.514)10,我们和前面一样变为 10 10 10 的幂次的和,便是 ( 114.514 ) 10 = 1 × 1 0 2 + 1 × 1 0 1 + 1 × 1 0 0 + 5 × 1 0 − 1 + 1 × 1 0 − 2 + 4 × 1 0 − 3 (114.514)_{10}=1\times10^2+1\times10^1+1\times10^0+5\times10^{-1}+1\times10^{-2}+4\times10^{-3} (114.514)10=1×102+1×101+1×100+5×10−1+1×10−2+4×10−3。
2-2. 二进制
如 ( 10.011 ) 2 (10.011)_2 (10.011)2,我们仍旧变为十进制下 2 2 2 的幂次的和,便是 ( 10.011 ) 2 = 1 × 2 1 + 0 × 2 0 + 0 × 2 − 1 + 1 × 2 − 2 + 1 × 2 − 3 (10.011)_2=1\times2^1+0\times2^0+0\times2^{-1}+1\times2^{-2}+1\times2^{-3} (10.011)2=1×21+0×20+0×2−1+1×2−2+1×2−3。
2-3. 二进制和十进制之间的转换
2-3-1. 二进制转十进制
整数部分参照 1-3-1。
小数部分其实也是一样的,按照 2-2 中的算式累加即可。
代码留给读者自行思考。
2-3-2. 十进制转二进制
和整数转换类似,但是我们是除以
2
−
1
2^{-1}
2−1,也就是
×
2
\times 2
×2。然后,我们取整数部分为数位(顺序取),小数部分再执行同样的操作,直到为
0
0
0。
以
(
0.125
)
10
(0.125)_{10}
(0.125)10 为例。
0.125
×
2
=
0.25
0.25
×
2
=
0.5
0.5
×
2
=
1
\begin{aligned} 0.125\times2=&0.25\\ 0.25\times2=&0.5\\ 0.5\times2=&1\\ \end{aligned}
0.125×2=0.25×2=0.5×2=0.250.51
于是得到:
(
0.125
)
10
=
(
0.001
)
2
(0.125)_{10}=(0.001)_2
(0.125)10=(0.001)2
#include<bits/stdc++.h>
using namespace std;
void f(int x)
{
if(!x) return;
f(x/2);
cout<<x%2;
}
int main()
{
double n;
cin>>n;
if(!(int)n) cout<<0;
else f((int)n);
n-=(int)n;//整数部分
if(n>0) cout<<'.';
while(n>0)
{
cout<<(int)(n*2);
n=n*2-(int)(n*2);
}//小数部分
return 0;
}
附:请读者尝试 ( 0.55 ) 10 , ( 0.18 ) 10 (0.55)_{10},(0.18)_{10} (0.55)10,(0.18)10 等数,试说明为什么计算机存储小数会有误差。
其余进制同理。
3. 八进制和十六进制
由于二进制过于冗长,于是方便起见,有时会用八或十六进制来表示数字。
3-1. 八进制
其遵循满八进一,用 0 ∼ 7 0\sim7 0∼7 的数字表示。通常会在数字前加入前缀 o o o 来表示这是一个八进制数。比如 ( 114514 ) 8 = o 114514 = 1 × 8 5 + 1 × 8 4 + 4 × 8 3 + 5 × 8 2 + 1 × 8 1 + 4 × 8 0 (114514)_8=o114514=1\times8^5+1\times8^4+4\times8^3+5\times8^2+1\times8^1+4\times8^0 (114514)8=o114514=1×85+1×84+4×83+5×82+1×81+4×80。进制转换是类似的,这里不再赘述。
3-2. 十六进制
其遵循满十六进一,用 0 ∼ 9 , A ( 11 ) ∼ F ( 15 ) 0\sim9,A(11)\sim F(15) 0∼9,A(11)∼F(15) 十六个字符表示。一般情况下 A ∼ F A\sim F A∼F 可以写作 a ∼ f a\sim f a∼f。通常会在数字前加入前缀 0 x 0x 0x 来表示这是一个十六进制数。比如 ( 1 E 45 E 4 ) 16 = 0 x 1 E 45 E 4 = 1 × 1 6 5 + E ( 14 ) × 1 6 4 + 4 × 1 6 3 + 5 × 1 6 2 + E ( 14 ) × 1 6 1 + 4 × 1 6 0 (1E45E4)_{16}=0x1E45E4=1\times16^5+E(14)\times16^4+4\times16^3+5\times16^2+E(14)\times16^1+4\times16^0 (1E45E4)16=0x1E45E4=1×165+E(14)×164+4×163+5×162+E(14)×161+4×160。进制转换是类似的,这里不再赘述。
3-3. 二、八、十六进制之间的转换
一种基本思路是先转为十进制,再转为要转的进制。
但是我们发现,八进制的一位就是二进制的三位(因为
2
3
=
8
2^3=8
23=8),十六进制的一位就是二进制的四位(因为
2
4
=
16
2^4=16
24=16)。反之,亦然。
这样就更简便了。
代码留给读者自行思考。
*4. 负进制
负进制仅在「NOIP2000 提高组 复赛」和 「CSP-S2022 初赛」中涉及到,频率不会太高,读者自行选读。
4-1. 负进制的定义
对于一个
k
k
k 进制的数
a
k
a_k
ak,若
k
<
0
k<0
k<0,则是负进制数。
注意,数码仍旧是
0
∼
k
−
1
0\sim k-1
0∼k−1。
比如
(
1101
)
−
2
(1101)_{-2}
(1101)−2。
根据上文,我们可以知道
(
1101
)
−
2
=
1
×
(
−
2
)
3
+
1
×
(
−
2
)
2
+
0
×
(
−
2
)
1
+
1
×
(
−
2
)
0
(1101)_{-2}=1\times(-2)^3+1\times(-2)^2+0\times(-2)^1+1\times(-2)^0
(1101)−2=1×(−2)3+1×(−2)2+0×(−2)1+1×(−2)0。
4-2. 负进制转十进制
还是以
(
1101
)
−
2
(1101)_{-2}
(1101)−2 为例。
得出
(
1101
)
−
2
=
1
×
(
−
2
)
3
+
1
×
(
−
2
)
2
+
0
×
(
−
2
)
1
+
1
×
(
−
2
)
0
=
−
8
+
4
+
1
=
(
−
3
)
10
(1101)_{-2}=1\times(-2)^3+1\times(-2)^2+0\times(-2)^1+1\times(-2)^0=-8+4+1=(-3)_{10}
(1101)−2=1×(−2)3+1×(−2)2+0×(−2)1+1×(−2)0=−8+4+1=(−3)10。
代码很简单,就不放了。
4-3. 十进制转负进制
仍然考虑短除。
但是程序实际编写时,会出现问题。
比如,我们希望
−
3
÷
−
2
=
2
,
−
3
m
o
d
−
2
=
1
-3\div-2=2,-3\bmod-2=1
−3÷−2=2,−3mod−2=1,但是程序得出的是
−
3
÷
−
2
=
1
,
−
3
m
o
d
−
2
=
−
1
-3\div-2=1,-3\bmod-2=-1
−3÷−2=1,−3mod−2=−1。因此,我们可以考虑将程序算出的余数减去除数,再将被除数加上除数,然后再往下做。
#include<bits/stdc++.h>
using namespace std;
const string R="0123456789ABCDEF";
int r;
void f(int x)
{
if(!x) return;
int p=x%r;
if(p<0) p-=r,x+=r;//处理特殊情况
f(x/r);
cout<<R[p];
}
int main()
{
int n;
cin>>n>>r;
if(!n) puts("0");
else f(n);
return 0;
}
4-4. 例题
4-4-1. Luogu P1017 [NOIP2000 提高组] 进制转换
结合上述代码修改一下即可。
代码就不放了。
*5. 平衡三进制
平衡三进制很少用到,读者自行选读。
5-1. 平衡三进制的定义
平衡三进制,也叫对称三进制。这是一个不太标准的计数体系。
其基数还是
3
3
3,但是平衡三进制的数是
−
1
,
0
,
1
-1,0,1
−1,0,1(方便起见,我们将
−
1
-1
−1 表示为
Z
\tt Z
Z 或
F
\tt F
F 或
T
\tt T
T,下文中我们使用
Z
\tt Z
Z)。
那我们可以表示:
十进制 | 平衡三进制 |
---|---|
⋯ \cdots ⋯ | ⋯ \cdots ⋯ |
− 5 -5 −5 | Z 11 \tt Z11 Z11 |
− 4 -4 −4 | Z Z \tt ZZ ZZ |
− 3 -3 −3 | Z 0 \tt Z0 Z0 |
− 2 -2 −2 | Z 1 \tt Z1 Z1 |
− 1 -1 −1 | Z \tt Z Z |
0 0 0 | 0 \tt 0 0 |
1 1 1 | 1 \tt 1 1 |
2 2 2 | 1 Z \tt 1Z 1Z |
3 3 3 | 10 \tt 10 10 |
4 4 4 | 11 \tt 11 11 |
5 5 5 | 1 Z Z \tt 1ZZ 1ZZ |
⋯ \cdots ⋯ | ⋯ \cdots ⋯ |
可以发现,对于 a ( a > 0 ) a(a>0) a(a>0) 的平衡三进制, − a -a −a 的平衡三进制只要将 a a a 的平衡三进制中 1 \tt1 1 变为 Z \tt Z Z、 Z \tt Z Z 变为 1 \tt1 1 即可。
5-2. 平衡三进制和十进制之间的转换
5-2-1. 平衡三进制转十进制
以
(
1
Z
011
)
3
\tt(1Z011)_3
(1Z011)3 为例。以十进制下
3
3
3 的幂次的和表示后计算即可。
(
1
Z
011
)
3
=
1
×
3
4
+
(
−
1
)
×
3
3
+
0
×
3
2
+
1
×
3
1
+
1
×
3
0
=
81
−
27
+
0
+
3
+
1
=
58.
\begin{aligned} &\tt(1Z011)_3\\ =&1\times3^4+(-1)\times3^3+0\times3^2+1\times3^1+1\times3^0\\ =&81-27+0+3+1\\ =&58. \end{aligned}
===(1Z011)31×34+(−1)×33+0×32+1×31+1×3081−27+0+3+158.
#include<bits/stdc++.h>
using namespace std;
int main()
{
string s;
cin>>s;
int ans=0;
for(int i=0;i<s.size();i++) ans=ans*3+(s[i]=='Z'?-1:s[i]-48);
cout<<ans;
return 0;
}
5-2-2. 十进制转平衡三进制
这有一点复杂。
首先考虑转成普通三进制,然后从低位起。
- 如果该位为 0 , 1 0,1 0,1,这属于平衡三进制的数码,忽略;
- 如果该位为 2 2 2,这并不是平衡三进制的数码。我们知道 2 = 3 − 1 2=3-1 2=3−1,因此,我们将这一位变成 Z \tt Z Z,下一位 + 1 +1 +1;
- 如果该位为 3 3 3(连续进位),这并不是平衡三进制的数码。我们知道 3 = 3 + 0 3=3+0 3=3+0,因此我们将这一位变成 0 0 0,下一位 + 1 +1 +1。
以
(
146
)
10
(146)_{10}
(146)10 为例。
(
146
)
10
=
(
12102
)
3
(146)_{10}=(12102)_3
(146)10=(12102)3。
- 第 1 1 1 位为 2 2 2,这位变成 Z \tt Z Z,下一位 + 1 +1 +1。 1211 Z \tt 1211Z 1211Z。
- 第 2 2 2 位为 1 1 1,忽视,继续。 1211 Z \tt 1211Z 1211Z。
- 第 3 3 3 位为 1 1 1,忽视,继续。 1211 Z \tt 1211Z 1211Z。
- 第 4 4 4 位为 2 2 2,这位变成 Z \tt Z Z,下一位 + 1 +1 +1。 2 Z 11 Z \tt 2Z11Z 2Z11Z。
- 第 5 5 5 位为 2 2 2,这位变成 Z \tt Z Z,下一位 + 1 +1 +1。 1 Z Z 11 Z \tt 1ZZ11Z 1ZZ11Z。
- 第 6 6 6 位为 1 1 1,忽视,继续。 1 Z Z 11 Z \tt 1ZZ11Z 1ZZ11Z。
- 结束。
#include<bits/stdc++.h>
using namespace std;
string f(int x)
{
if(!x) return "";
return (char)(x%3+48)+f(x/3);
}
int main()
{
int n;
cin>>n;
if(!n)
{
puts("0");
return 0;
}
string s=f(n)+"0";
for(int i=0;i<s.size();i++)
{
if(s[i]=='2') s[i]='Z',s[i+1]=s[i+1]=='Z'?'0':s[i+1]+1;
if(s[i]=='3') s[i]='0',s[i+1]=s[i+1]=='Z'?'0':s[i+1]+1;
}
reverse(s.begin(),s.end());
cout<<s;
return 0;
}
5-3. 平衡三进制的唯一性
平衡三进制的唯一性,是指,每个十进制数都唯一对应一个平衡三进制数。
这里我们只考虑整数,实数同理。
考虑反证法。
假设存在一个平衡三进制数
X
3
X_3
X3 和一个十进制数
Y
10
Y_{10}
Y10。
如果不唯一对应,则存在不同的平衡三进制数
A
3
,
B
3
A_3,B_3
A3,B3 满足
A
3
=
B
3
=
Y
10
A_3=B_3=Y_{10}
A3=B3=Y10。
- 当 Y 10 = 0 Y_{10}=0 Y10=0 时, A 3 = B 3 = 0 3 A_3=B_3=0_3 A3=B3=03,矛盾。
- 当
Y
10
>
0
Y_{10}>0
Y10>0 时:
- 若将
A
3
,
B
3
A_3,B_3
A3,B3 数位从低到高编号,设
a
i
,
b
i
a_i,b_i
ai,bi 分别为
A
3
,
B
3
A_3,B_3
A3,B3 的第
i
i
i 位。那么必定存在一个
i
i
i,使得
a
i
≠
b
i
a_i\neq b_i
ai=bi。而
a
0
∼
i
−
1
,
b
0
∼
i
−
1
a_{0\sim i-1},b_{0\sim i-1}
a0∼i−1,b0∼i−1 与其无关,因此我们考虑截掉得到
A
3
′
,
B
3
′
A'_3,B'_3
A3′,B3′,即证
A
3
′
=
B
3
′
A'_3=B'_3
A3′=B3′。
设 a i ′ , b i ′ a'_i,b'_i ai′,bi′ 分别为 A 3 ′ , B 3 ′ A'_3,B'_3 A3′,B3′ 的第 i i i 位。我们知道 a 0 ′ ≠ b 0 ′ a'_0\neq b'_0 a0′=b0′。假设 a 0 ′ < b 0 ′ a'_0<b'_0 a0′<b0′,则 b 0 ′ − a 0 ′ ∈ { 1 , 2 } b'_0-a'_0\in\{1,2\} b0′−a0′∈{1,2},得出 b 0 ′ − a 0 ′ ∤ 3 b'_0-a'_0\nmid3 b0′−a0′∤3。令 S 1 = a 1 ′ × 3 1 + a 2 ′ × 3 2 + ⋯ , 2 = b 1 ′ × 3 1 + b 2 ′ × 3 2 + ⋯ S_1=a'_1\times3^1+a'_2\times3^2+\cdots,2=b'_1\times3^1+b'_2\times3^2+\cdots S1=a1′×31+a2′×32+⋯,2=b1′×31+b2′×32+⋯。先前我们知道 A 3 = B 3 A_3=B_3 A3=B3,而且由此得出 A 3 ′ = B 3 ′ A'_3=B'_3 A3′=B3′,因此 S 1 − S 2 = b 0 − a 0 S_1-S_2=b_0-a_0 S1−S2=b0−a0。 S 1 ∣ 3 , S 2 ∣ 3 S_1\mid3,S_2\mid3 S1∣3,S2∣3,因此 S 1 − S 2 ∣ 3 S_1-S_2\mid3 S1−S2∣3,因此 b 0 − a 0 ∣ 3 b_0-a_0\mid3 b0−a0∣3,但是这与先前从假设退出的结论矛盾,因此成立。
- 若将
A
3
,
B
3
A_3,B_3
A3,B3 数位从低到高编号,设
a
i
,
b
i
a_i,b_i
ai,bi 分别为
A
3
,
B
3
A_3,B_3
A3,B3 的第
i
i
i 位。那么必定存在一个
i
i
i,使得
a
i
≠
b
i
a_i\neq b_i
ai=bi。而
a
0
∼
i
−
1
,
b
0
∼
i
−
1
a_{0\sim i-1},b_{0\sim i-1}
a0∼i−1,b0∼i−1 与其无关,因此我们考虑截掉得到
A
3
′
,
B
3
′
A'_3,B'_3
A3′,B3′,即证
A
3
′
=
B
3
′
A'_3=B'_3
A3′=B3′。
- 当 Y 10 < 0 Y_{10}<0 Y10<0 时,做法与上述同理。
因此平衡三进制的唯一性成立。
6. 二进制位运算入门
6-1. 位运算的定义
位运算是基于整数的二进制表示进行的运算。基本的位运算主要分为按位与、按位或、按位异或、按位取反、左移和右移。
6-2. 原码、反码、补码
计算机中存储的有符号整数 a a a,我们规定:
- 原码:将 a a a 表示成符号位 + 二进制串。符号位上, 0 0 0 表示正数, 1 1 1 表示负数。故有 + 0 , − 0 +0,-0 +0,−0 之分。
- 反码:
- 若 a ≥ 0 a\ge0 a≥0,反码就是其原码。
- 若 a < 0 a<0 a<0,反码就是除符号位外,原码的各位全部取反,即 1 1 1 变 0 0 0, 0 0 0 变 1 1 1。
- 补码:
- 若 a ≥ 0 a\ge0 a≥0,补码就是其原码
- 若 a < 0 a<0 a<0,补码就是 反码 + 1 \footnotesize\texttt{反码}\normalsize+1 反码+1。
6-3. 基本运算
6-3-1. 按位与、按位或、按位异或
运算 | 数学符号 / 运算符 | 意义 |
---|---|---|
按位与 | and , & \text{and},\& and,& | 两个对应位均为 1 1 1 时才为 1 1 1 |
按位或 | or , ∣ \text{or},\mid or,∣ | 两个对应位存在 1 1 1 时才为 1 1 1 |
按位异或 | xor , ⊕ \text{xor},\oplus xor,⊕ | 两个对应位值不同时才为 1 1 1 |
我们采用逻辑运算的真值表来分别看一下:
& \& & | 0 0 0 | 1 1 1 |
---|---|---|
0 0 0 | 0 0 0 | 0 0 0 |
1 1 1 | 0 0 0 | 1 1 1 |
∣ \mid ∣ | 0 0 0 | 1 1 1 |
---|---|---|
0 0 0 | 0 0 0 | 1 1 1 |
1 1 1 | 1 1 1 | 1 1 1 |
⊕ \oplus ⊕ | 0 0 0 | 1 1 1 |
---|---|---|
0 0 0 | 0 0 0 | 1 1 1 |
1 1 1 | 1 1 1 | 0 0 0 |
下面是几个例子:
- 5 & 3 = ( 101 ) 2 & ( 11 ) 2 = ( 1 ) 2 = 1 5\&3=(101)_2\&(11)_2=(1)_2=1 5&3=(101)2&(11)2=(1)2=1。
- 5 ∣ 3 = ( 101 ) 2 ∣ ( 11 ) 2 = ( 111 ) 2 = 7 5\mid3=(101)_2\mid(11)_2=(111)_2=7 5∣3=(101)2∣(11)2=(111)2=7。
- 5 ⊕ 3 = ( 101 ) 2 ⊕ ( 11 ) 2 = ( 110 ) 2 = 6 5\oplus3=(101)_2\oplus(11)_2=(110)_2=6 5⊕3=(101)2⊕(11)2=(110)2=6。
6-3-2. 按位取反
按位取反是一个单目运算,表示为
∼
a
\sim a
∼a,其意义是把
a
a
a 的补码每一位取反。
下面是几个例子:
- ∼ 5 = ∼ ( 00000101 ) 2 = ( 11111010 ) 2 = − 6 \sim5=\sim(00000101)_2=(11111010)_2=-6 ∼5=∼(00000101)2=(11111010)2=−6。
- ∼ ( − 5 ) = ∼ ( 11111011 ) 2 = ( 00000100 ) 2 = 4 \sim(-5)=\sim(11111011)_2=(00000100)_2=4 ∼(−5)=∼(11111011)2=(00000100)2=4。
6-3-3. 左移、右移
左移是指将一个数在二进制下同时向左移动,低位以
0
0
0 填充,高位越界后舍弃。
右移分为算术右移和逻辑右移:
- 算术右移:将一个数在二进制表示下同时向右移动,高位以符号位填充,低位越界后舍弃。
- 逻辑右移:将一个数在二进制表示下同时向右移动,高位以 0 \textbf 0 0 填充,低位越界后舍弃。
我们通常使用算术右移,下文的右移均为算术右移。
6-4. 位运算的简单应用
- a ≪ n = 2 n a a\ll n=2^na a≪n=2na。
- a ≫ n = ⌊ a 2 n ⌋ a\gg n=\lfloor\dfrac{a}{2^n}\rfloor a≫n=⌊2na⌋。
- 当 x = y x=y x=y 时, x ⊕ y = 0 x\oplus y=0 x⊕y=0。
- 在整数情况下,
x^=y^=x^=y;
等价于swap(x,y);
。
6-5. 二进制状态压缩
6-5-1. 二进制状态压缩的定义和用法
二进制状态压缩,简称状压,是指将一个长度为
m
m
m 的 bool 数组用一个
m
m
m 位的二进制整数表示的方法。
注意,我们通常认为二进制最低位是第
0
0
0 位,而后递增。
下面是一些常用技巧:
操作 | 运算 |
---|---|
获取 n n n 二进制的第 k k k 位 | ( n ≫ k ) & 1 (n\gg k)\&1 (n≫k)&1 |
获取 n n n 二进制的后 k k k 位 | n & ( ( 1 ≪ k ) − 1 ) n\&((1\ll k)-1) n&((1≪k)−1) |
将 n n n 二进制的第 k k k 位取反 | n xor ( 1 ≪ k ) n\text{ xor }(1\ll k) n xor (1≪k) |
将 n n n 二进制的第 k k k 位变为 1 1 1 | n ∣ ( 1 ≪ k ) n\mid(1\ll k) n∣(1≪k) |
将 n n n 二进制的第 k k k 位变为 0 0 0 | n & ( ∼ ( 1 ≪ k ) ) n\&(\sim(1\ll k)) n&(∼(1≪k)) |
6-5-2. bitset
当
m
m
m 过大时,可以使用 bitset。
bitset 严格意义上不算 C++ STL,但是你也可以把它当 STL 看。
以下代码定义了一个 长度为
1000
1000
1000、名为
b
b
b 的 bitset:
bitset<1000>b;
以下是 bitset 的常用函数:
size()
:返回长度。count()
:返回 1 1 1 的数量。any()
:返回是否包含 1 1 1。none()
:返回是否全 0 0 0。all()
:返回是否全 1 1 1。set(x)
:将第 x x x 位设为 1 1 1。若没有 x x x,则全部设为 1 1 1。reset(x)
:将第 x x x 位设为 0 0 0。若没有 x x x,则全部设为 0 0 0。flip(x)
:将第 x x x 位翻转。若没有 x x x,则全部翻转。to_string()
:返回其 01 01 01 字符串表示。
还有一些不常用函数这里就不展示了,感兴趣的读者可以查询相关资料。
6-6. lowbit()
我们定义
lowbit
(
x
)
\text{lowbit}(x)
lowbit(x) 为
x
(
x
≥
0
)
x(x\ge0)
x(x≥0) 在二进制表示下最低位的
1
1
1 和其后面所有的
0
0
0 构成的数值。如
lowbit
(
12
)
=
lowbit
(
(
1100
)
2
)
=
(
100
)
2
=
4
\text{lowbit}(12)=\text{lowbit}((1100)_2)=(100)_2=4
lowbit(12)=lowbit((1100)2)=(100)2=4。
C++ 中并没有这个函数,但是我们可以通过推导知道:
lowbit
(
x
)
=
x
&
(
−
x
)
\text{lowbit}(x)=x\&(-x)
lowbit(x)=x&(−x)。
6-7. 例题
6-7-1. Luogu P4289 [HAOI2008]移动玩具
考虑 bfs。
队列内我们要存储步数和状态,而这个状态就可以用二进制来状压一下。
然后为了避免重复,我们只将一个玩具向左或向上交换。
参考代码:
#include<bits/stdc++.h>
#define pos(x,y) (x*4+y)
#define w(n,k) ((n>>k)&1)//n 二进制的第 k 位
#define f(n,k) (n^(1<<k))//n 二进制的第 k 位取反
using namespace std;
const int X[]={0,-1};
const int Y[]={-1,0};
int a,b,f[100005];
queue<pair<int,int>>q;
int bfs()
{
q.push({a,1});
f[a]=1;
while(!q.empty())
{
int x=q.front().first,dep=q.front().second;
q.pop();
for(int i=0;i<4;i++)
{
for(int j=0;j<4;j++)
{
for(int k=0;k<2;k++)
{
int x_=i+X[k],y_=j+Y[k];
if(x_<0||y_<0||x_>3||y_>3||w(x,pos(i,j))==w(x,pos(x_,y_))) continue;
x=f(x,pos(i,j));
x=f(x,pos(x_,y_));
if(x==b)//搜到答案
{
cout<<dep;
exit(0);
}
if(!f[x])
{
q.push({x,dep+1});
f[x]=1;
}
x=f(x,pos(i,j));
x=f(x,pos(x_,y_));//回溯
}
}
}
}
}
int main()
{
for(int i=1;i<=16;i++)
{
char c;
cin>>c;
a=(a<<1)|c-48;//存储二进制数,下同
}
for(int i=1;i<=16;i++)
{
char c;
cin>>c;
b=(b<<1)|c-48;
}
cout<<(a==b?0:bfs());//相等要特判
return 0;
}
7. 巩固练习
a m o d b a\bmod b amodb 表示 a a a 除以 b b b 的余数,等价于 C++ 中的
a%b
。 ↩︎