「数学与数论详解·一」进制

本文详细介绍了数学与数论中关于进制转换的内容,包括十进制、二进制、八进制、十六进制的转换,以及负进制和平衡三进制的概念和转换方法。此外,还探讨了二进制位运算的基本操作,如按位与、按位或、按位异或、按位取反、左移和右移,并给出了相关例题和代码示例。
摘要由CSDN通过智能技术生成
  • 「数学与数论详解·一」进制

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×101+1×102+4×103

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×21+1×22+1×23

2-3. 二进制和十进制之间的转换

2-3-1. 二进制转十进制

整数部分参照 1-3-1。
小数部分其实也是一样的,按照 2-2 中的算式累加即可。
代码留给读者自行思考。

2-3-2. 十进制转二进制

和整数转换类似,但是我们是除以 2 − 1 2^{-1} 21,也就是 × 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 07 的数字表示。通常会在数字前加入前缀 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) 09,A(11)F(15) 十六个字符表示。一般情况下 A ∼ F A\sim F AF 可以写作 a ∼ f a\sim f af。通常会在数字前加入前缀 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 0k1
比如 ( 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,3mod2=1,但是程序得出的是 − 3 ÷ − 2 = 1 , − 3   m o d   − 2 = − 1 -3\div-2=1,-3\bmod-2=-1 3÷2=1,3mod2=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×308127+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=31,因此,我们将这一位变成 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} a0i1,b0i1 与其无关,因此我们考虑截掉得到 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\} b0a0{1,2},得出 b 0 ′ − a 0 ′ ∤ 3 b'_0-a'_0\nmid3 b0a03。令 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 S1S2=b0a0 S 1 ∣ 3 , S 2 ∣ 3 S_1\mid3,S_2\mid3 S13,S23,因此 S 1 − S 2 ∣ 3 S_1-S_2\mid3 S1S23,因此 b 0 − a 0 ∣ 3 b_0-a_0\mid3 b0a03,但是这与先前从假设退出的结论矛盾,因此成立。
  • 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 a0,反码就是其原码。
    • a < 0 a<0 a<0,反码就是除符号位外,原码的各位全部取反,即 1 1 1 0 0 0 0 0 0 1 1 1
  • 补码
    • a ≥ 0 a\ge0 a0,补码就是其原码
    • 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 53=(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 53=(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 an=2na
  • a ≫ n = ⌊ a 2 n ⌋ a\gg n=\lfloor\dfrac{a}{2^n}\rfloor an=2na
  • x = y x=y x=y 时, x ⊕ y = 0 x\oplus y=0 xy=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 (nk)&1
获取 n n n 二进制的后 k k k n & ( ( 1 ≪ k ) − 1 ) n\&((1\ll k)-1) n&((1k)1)
n n n 二进制的第 k k k 位取反 n  xor  ( 1 ≪ k ) n\text{ xor }(1\ll k) n xor (1k)
n n n 二进制的第 k k k 位变为 1 1 1 n ∣ ( 1 ≪ k ) n\mid(1\ll k) n(1k)
n n n 二进制的第 k k k 位变为 0 0 0 n & ( ∼ ( 1 ≪ k ) ) n\&(\sim(1\ll k)) n&((1k))

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(x0) 在二进制表示下最低位的 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. 巩固练习



  1. a   m o d   b a\bmod b amodb 表示 a a a 除以 b b b 的余数,等价于 C++ 中的 a%b↩︎

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值