第8章 简单算法与数学

第8章 简单算法与数学

本章是 GESP 一级的"算法入门":进制转换、素数判断、最大公约数/最小公倍数、数位分离,以及把上述技巧组合起来的简单模拟题。

8.1 进制与相互转换

计算机内部用二进制,但人常用十进制,编程中也常用十六进制。要掌握它们之间的转换。

8.1.1 十进制 → 二进制(除 2 取余法)

把十进制数不断除以 2,记下每次的余数,最后把余数倒序排列。

例:把 1313 转成二进制

步骤商余数
13 ÷ 261
6 ÷ 230
3 ÷ 211
1 ÷ 201

余数倒序为 1101,所以 13=(1101)213 = (1101)_2。

// 把十进制 n 转成二进制输出(逆序收集后输出)
#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n;
int bits[32], k = 0;
if (n == 0) cout << 0;
while (n > 0)
{
bits[k++] = n % 2; // 取余
n /= 2; // 除 2
}
for (int i = k - 1; i >= 0; i--) // 倒序输出
cout << bits[i];
return 0;
}

8.1.2 二进制 → 十进制(按权展开)

把每一位乘以 22 的"位权"再相加。例如 (1101)2(1101)_2:

(1×23)+(1×22)+(0×21)+(1×20)=8+4+0+1=13(1\times2^3) + (1\times2^2) + (0\times2^1) + (1\times2^0) = 8+4+0+1 = 13

8.1.3 八进制与十六进制(简介)

进制数字符号前缀
八进制0~70(C++ 字面量)
十六进制09, AF(10~15)0x
  • 十六进制中 A=10, B=11, ..., F=15。
  • 十进制转十六进制同样用"除 16 取余",余数 1015 对应字母 AF。
tip

11 位十六进制 = 44 位二进制,所以二进制和十六进制互转非常方便:1101 1010 → DA。考试中常以"填空/选择"形式考查,掌握 0∼150\sim15 的对应即可。

8.2 素数(质数)判断

素数:大于 1,且只能被 1 和自身整除的整数(如 2,3,5,7,11)。

试除法:判断 n 是否为素数,只需用 2∼n2\sim\sqrt{n} 的整数去试除,若都除不尽则为素数。

#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int n;
cin >> n;
bool isPrime = true;
if (n < 2) isPrime = false;
for (int i = 2; i <= sqrt(n); i++)
{
if (n % i == 0) // 找到能整除的因子
{
isPrime = false;
break;
}
}
cout << (isPrime ? "prime" : "not prime") << endl;
return 0;
}
note

只需试到 n\sqrt{n} 的原因:若 n 有因子,必有一个 ≤n\le \sqrt{n}。这把判断次数从 nn 降到 n\sqrt{n},效率高很多。

8.3 最大公约数(GCD)与最小公倍数(LCM)

8.3.1 辗转相除法(欧几里得算法)

求 a,ba,b 的最大公约数:不断用"较小数"去除"较大数"取余,直到余数为 0,最后的除数就是 GCD。

例:求 gcd⁡(48,18)\gcd(48, 18)

  • 48÷18=248 \div 18 = 2 余 1212
  • 18÷12=118 \div 12 = 1 余 66
  • 12÷6=212 \div 6 = 2 余 00 → GCD = 6
int gcd(int a, int b)
{
while (b != 0)
{
int t = a % b;
a = b;
b = t;
}
return a; // 余数为 0 时的 a 即最大公约数
}

8.3.2 最小公倍数

LCM(a,b)=a×bgcd⁡(a,b)\text{LCM}(a,b) = \frac{a \times b}{\gcd(a,b)}

int lcm(int a, int b)
{
return a / gcd(a, b) * b; // 先除后乘,避免 a*b 溢出
}
caution

直接写 a * b / gcd 可能整数溢出(尤其 int)。先 a / gcd 再乘 b 更安全。

8.4 数位分离

把整数的每一位拆出来:反复"取个位 + 去掉个位"。

int n = 12345;
while (n > 0)
{
int d = n % 10; // 当前个位
cout << d << " "; // 从个位开始输出:5 4 3 2 1
n /= 10; // 去掉个位
}
tip

数位分离 + 取余判断是很多题的核心:判断回文数、求各位数字之和、统计某位数字出现次数等。

8.5 累加、累乘与最值

模式写法
累加sum += x;
累乘prod *= x;
最大值if (x > maxv) maxv = x;
最小值if (x < minv) minv = x;
// 求 n 的阶乘 n!(累乘)
int n, f = 1;
cin >> n;
for (int i = 1; i <= n; i++)
f *= i;
cout << f << endl;

8.6 简单模拟

"模拟"就是题目怎么说,程序就怎么写,把自然语言步骤翻译成代码。下面用一个综合例子串起"累加 + 数位分离 + 取余"。

【示例】输入一个正整数 n,判断它是否为"水仙花数"(三位数且各位立方和等于自身)。

#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n;
int t = n, sum = 0;
while (t > 0)
{
int d = t % 10; // 分离一位
sum += d * d * d; // 立方累加
t /= 10;
}
cout << (sum == n ? "yes" : "no") << endl;
return 0;
}

【示例】输入 n,输出 1∼n1\sim n 中所有素数(综合:循环 + 素数判断)。

#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int n;
cin >> n;
for (int x = 2; x <= n; x++)
{
bool ok = true;
for (int i = 2; i <= sqrt(x); i++)
if (x % i == 0) { ok = false; break; }
if (ok) cout << x << " ";
}
return 0;
}

8.7 随堂检测

1. 十进制数 1313 的二进制表示是( )。

  • A. 1011
  • B. 1101
  • C. 1110
  • D. 1001
查看答案

答案:B。13=8+4+1=(1101)213 = 8+4+1 = (1101)_2。

2. 判断素数时,试除只需到( )即可。

  • A. nn
  • B. n/2n/2
  • C. n\sqrt{n}
  • D. n−1n-1
查看答案

答案:C。若 n 有因子,必有一个不超过 n\sqrt{n}。

3. 已知 gcd⁡(48,18)=6\gcd(48,18)=6,则 lcm(48,18)=\text{lcm}(48,18)=( )。

  • A. 144
  • B. 864
  • C. 6
  • D. 24
查看答案

答案:A。LCM=48×18/6=144\text{LCM} = 48 \times 18 / 6 = 144。

SShowball OI知识手册