题解:洛谷 P1045 麦森数
【题目来源】
洛谷:P1045 [NOIP 2003 普及组] 麦森数 - 洛谷(luogu.com.cn)
【题目描述】
形如 \(2^P-1\) 的素数称为麦森数,这时 \(P\) 一定也是个素数。但反过来不一定,即如果 \(P\) 是个素数,\(2^P-1\) 不一定也是素数。到 \(1998\) 年底,人们已找到了 \(37\) 个麦森数。最大的一个是 \(P=3021377\),它有 $909526 $ 位。麦森数有许多重要应用,它与完全数密切相关。
任务:\(P(1000\lt P\lt 3100000)\),计算 \(2^P-1\) 的位数和最后 \(500\) 位数字(用十进制高精度数表示)
【输入】
文件中只包含一个整数 \(P(1000\lt P\lt 3100000)\)
【输出】
第一行:十进制高精度数 \(2^P-1\) 的位数。
第 \(2∼11\) 行:十进制高精度数 \(2^P-1\) 的最后 \(500\) 位数字。(每行输出 \(50\) 位,共输出 \(10\) 行,不足 500 位时高位补 0)
不必验证 \(2^P-1\) 与 \(P\) 是否为素数。
【输入样例】
1279
【输出样例】
386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087
【核心思想】
-
问题分析:计算 \(2^P - 1\) 的位数和最后 500 位数字,其中 \(P\) 是一个大整数(\(1000 < P < 3,100,000\))。由于 \(2^P\) 是一个极大的数,直接计算不现实,需要利用数学公式和高精度技巧。
-
算法选择:
- 位数计算:\(2^P\) 的位数 = \(\lfloor P \times \log_{10}2 \rfloor + 1\),因为 \(10^{k-1} \le 2^P < 10^k\)。
- 最后 500 位计算:只需计算 \(2^P\) 的最后 500 位,然后减 1。使用高精度乘法,但为了加速,将乘法分块:先计算 \(2^{32}\),然后循环乘以 \(2^{32}\) 若干次,最后再乘以 \(2\) 的余数次幂。
- 复杂度:\(O(500 \times (P/32 + P \bmod 32))\),对于 \(P \le 3.1\times10^6\),约 \(500 \times 10^5\) 次操作,可接受。
-
关键步骤:
- 位数:输出
(int)(p * log10(2) + 1)。 - 计算最后 500 位:
- 用数组
a[1..500]存储每位数字(a[1]为最高位,a[500]为最低位)。 - 初始
a[500] = 1(表示数值 1)。 - 将 \(P\) 分解为
num1 = P / 32和num2 = P % 32。 - 先循环
num1次,每次将整个数组乘以 \(2^{32}\)(即a[j] *= 2^32,然后处理进位)。 - 再循环
num2次,每次将数组乘以 2。 - 最后
a[500]--(处理减 1 可能导致的借位,但此处因为只影响最后一位,简单处理即可)。
- 用数组
- 输出:按每行 50 位输出数组
a[1..500]。
- 位数:输出
-
时间/空间复杂度:
- 时间复杂度:\(O(500 \times (P/32 + P \bmod 32))\)。
- 空间复杂度:\(O(500)\)。
-
高精度与对数:
- 位数公式:使用对数计算位数是处理大数位数的标准方法。
- 分块乘法:用 \(2^{32}\) 作为基数,大幅减少乘法次数(从 \(P\) 次减少到 \(P/32\) 次)。
- 进位处理:从低位到高位依次处理进位,确保每位数在 0~9 之间。
- 最后 500 位补零:如果实际位数不足 500,高位补 0,这正好与数组初始化为 0 一致。
- 适用场景:这类“大数幂的位数和末几位”问题,是数学与高精度结合的经典题型。
【算法标签】
普及 #高精度
【代码详解】
#include <iostream>
#include <cmath>
using namespace std;
long long a[501] = {0}; // 数组a用于存储高精度计算结果,初始化为0
// 计算2的n次方
long long cp(int n)
{
long long result = 1;
for (int i = 1; i <= n; i++)
result *= 2; // 每次乘以2
return result; // 返回2的n次方
}
int main()
{
int p; // 输入的指数p
cin >> p; // 输入p
// 将p分解为num1和num2,其中num1是p除以32的商,num2是余数
int num1 = p / 32; // num1表示需要乘以2^32的次数
int num2 = p - num1 * 32; // num2表示剩余的乘法次数(每次乘以2)
long long x = cp(32); // 计算2^32的值
a[500] = 1; // 初始化数组a的最后一位为1(表示初始值为1)
/**
* @brief
* 为了加快计算速度,我们将乘法分为两部分:
* 1. 前num1轮,每次乘以2^32
* 2. 后num2轮,每次乘以2
*/
// 前num1轮,每次乘以2^32
for (int i = 1; i <= num1; i++)
{
// 对数组a的每一位乘以2^32
for (int j = 500; j >= 1; j--)
a[j] *= x;
// 处理进位
for (int j = 500; j >= 1; j--)
{
a[j - 1] += a[j] / 10; // 将进位加到前一位
a[j] %= 10; // 当前位只保留个位数
}
}
// 后num2轮,每次乘以2
for (int i = 1; i <= num2; i++)
{
// 对数组a的每一位乘以2
for (int j = 500; j >= 1; j--)
a[j] *= 2;
// 处理进位
for (int j = 500; j >= 1; j--)
{
a[j - 1] += a[j] / 10; // 将进位加到前一位
a[j] %= 10; // 当前位只保留个位数
}
}
a[500]--; // 最后一位减1(因为2^p - 1)
// 输出2^p - 1的位数
cout << (int)(p * log10(2) + 1) << endl;
// 输出2^p - 1的值,每50位换行
for (int i = 1; i <= 500; i++)
{
cout << a[i];
if (i % 50 == 0) // 每50位换行
cout << endl;
}
return 0;
}
【运行结果】
1279
386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087
浙公网安备 33010602011771号