当前位置: 首页 > news >正文

洛谷P1045 [NOIP 2003 普及组] 麦森数

题目描述

形如 2P−1 的素数称为麦森数,这时 P 一定也是个素数。但反过来不一定,即如果 P 是个素数,2P−1 不一定也是素数。到 1998 年底,人们已找到了 37 个麦森数。最大的一个是 P=3021377,它有 909526 位。麦森数有许多重要应用,它与完全数密切相关。

任务:输入 P(1000<P<3100000),计算 2P−1 的位数和最后 500 位数字(用十进制高精度数表示)

输入格式

文件中只包含一个整数 P(1000<P<3100000)

输出格式

第一行:十进制高精度数 2P−1 的位数。

第 2∼11 行:十进制高精度数 2P−1 的最后 500 位数字。(每行输出 50 位,共输出 10 行,不足 500 位时高位补 0)

不必验证 2P−1 与 P 是否为素数。

输入输出样例

输入 #1复制

1279

输出 #1复制

386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087

说明/提示

【题目来源】

NOIP 2003 普及组第四题


题目分析

麦森数是指形如 2^p - 1 的数,其中 p 是一个素数。本题要求计算 2^p - 1 的位数以及最后500位数字(不足500位时前面补0)。

难点分析

  1. 大数计算:当 p 很大时(题目中 p 最大为3100000),2^p 的值极其巨大,无法用常规数据类型存储。

  2. 高效计算:直接进行 p 次乘法会超时,需要使用快速幂算法。

  3. 位数计算:不需要实际计算整个数就能确定位数。

  4. 存储优化:只需存储最后500位,可以节省空间。

数学原理

位数计算

对于 2^p - 1 的位数,我们可以利用对数性质:

设 N = 2^p - 1,由于 2^p 远大于1,所以 N ≈ 2^p。

根据对数性质:log10(2^p) = p · log10(2)

数的位数 d = floor(log10(N)) + 1 ≈ floor(p · log10(2)) + 1

快速幂算法

快速幂算法通过二分思想将时间复杂度从 O(p) 降低到 O(log p):

  • 当 p 为偶数时:a^p = (a^(p/2))^2

  • 当 p 为奇数时:a^p = a · (a^((p-1)/2))^2

算法设计

数据结构

使用整型数组存储大数,每个元素存储若干位数字(这里选择每元素存储1位数字,便于理解)。

核心算法

  1. 计算位数:直接使用公式 d = floor(p · log10(2)) + 1

  2. 计算最后500位:使用快速幂算法,只保留最后500位

  3. 减法处理:计算 2^p - 1,注意借位问题

代码实现

cpp

代码详细解析

头文件说明

cpp

#include <iostream>
#include <cmath>
#include <cstring>
using namespace std;// 大数乘法(只保留最后500位)
// a: 第一个大数数组(每位存储一个数字)
// b: 第二个大数数组(每位存储一个数字)
void mul(int a[], int b[]) {int tmp[1005] = {0}; // 临时结果数组,初始化为0// 模拟竖式乘法,计算每一位的乘积for (int i = 0; i < 500; i++) {for (int j = 0; j < 500; j++) {if (i + j < 1000) { // 只计算可能影响最后500位的部分tmp[i + j] += a[i] * b[j];}}}// 处理进位,只保留最后500位for (int i = 0; i < 500; i++) {if (i < 999) {tmp[i + 1] += tmp[i] / 10; // 进位到高位}tmp[i] %= 10; // 保留个位数}// 将结果复制回a数组(只取最后500位)for (int i = 0; i < 500; i++) {a[i] = tmp[i];}
}// 快速幂计算 2^p
// p: 指数
void qpow(int p) {int res[1005] = {0}; // 结果数组,存储最终结果int base[1005] = {0}; // 底数数组,存储当前底数// 初始化:res = 1, base = 2res[0] = 1;  // 个位为1base[0] = 2; // 个位为2// 快速幂算法核心while (p > 0) {if (p % 2 == 1) {mul(res, base); // 如果指数是奇数,结果乘以当前底数}mul(base, base); // 底数平方p /= 2; // 指数减半}// 执行减法:2^p - 1res[0] -= 1; // 个位减1// 处理借位for (int i = 0; i < 500; i++) {if (res[i] < 0) {res[i] += 10; // 借位res[i + 1] -= 1;}}// 输出最后500位,每50位换行for (int i = 499; i >= 0; i--) {cout << res[i];if (i % 50 == 0 && i != 0) {cout << endl;}}
}int main() {int p;cin >> p;// 计算位数:使用对数公式int digits = (int)(p * log10(2)) + 1;cout << digits << endl;// 计算并输出最后500位qpow(p);return 0;
}

大数乘法函数 mul

这是整个程序的核心函数,负责处理大数乘法运算:

cpp

void mul(int a[], int b[]) {int tmp[1005] = {0}; // 临时数组,大小1005确保有足够空间

临时数组 tmp 用于存储乘法运算的中间结果。我们分配1005个元素是为了确保有足够的空间处理进位,即使两个500位的数相乘也不会溢出。

cpp

    for (int i = 0; i < 500; i++) {for (int j = 0; j < 500; j++) {if (i + j < 1000) {tmp[i + j] += a[i] * b[j];}}}

这部分代码模拟手工竖式乘法。外层循环遍历第一个数的每一位,内层循环遍历第二个数的每一位。a[i] * b[j] 的结果应该放在 tmp[i+j] 位置,因为这是相应位的乘积。

条件 i+j < 1000 是一个优化,只计算可能影响最后500位的乘积,因为更高位的结果最终会被舍弃。

cpp

    for (int i = 0; i < 500; i++) {if (i < 999) {tmp[i + 1] += tmp[i] / 10;}tmp[i] %= 10;}

这部分处理进位。对于每一位,如果值大于等于10,就将十位部分加到下一位,只保留个位在当前位。

cpp

    for (int i = 0; i < 500; i++) {a[i] = tmp[i];}
}

最后将临时数组的前500位复制回原数组,完成乘法运算。

快速幂函数 qpow

这个函数使用快速幂算法高效计算 2^p:

cpp

void qpow(int p) {int res[1005] = {0}; // 结果数组int base[1005] = {0}; // 底数数组

res 数组存储最终结果,初始化为1(2^0 = 1)。base 数组存储当前底数,初始化为2。

cpp

    res[0] = 1;base[0] = 2;

初始化结果和底数。使用数组的0索引表示个位,这是大数存储的常见方式。

cpp

    while (p > 0) {if (p % 2 == 1) {mul(res, base);}mul(base, base);p /= 2;}

这是快速幂算法的核心循环:

  • 如果当前指数p是奇数,将结果乘以当前底数

  • 无论指数奇偶,底数都要平方

  • 指数除以2(向下取整)

通过这种方式,算法复杂度从O(p)降低到O(log p)。

cpp

    res[0] -= 1;for (int i = 0; i < 500; i++) {if (res[i] < 0) {res[i] += 10;res[i + 1] -= 1;}}

计算 2^p - 1,从个位开始减1,并处理可能的借位。

cpp

    for (int i = 499; i >= 0; i--) {cout << res[i];if (i % 50 == 0 && i != 0) {cout << endl;}}
}

逆序输出结果数组(因为数组是低位在前),每50位换行,满足题目输出格式要求。

主函数 main

cpp

int main() {int p;cin >> p;int digits = (int)(p * log10(2)) + 1;cout << digits << endl;qpow(p);return 0;
}

主函数逻辑清晰:

  1. 读入指数p

  2. 计算并输出位数

  3. 调用快速幂函数计算并输出最后500位

算法优化分析

时间复杂度

  • 位数计算:O(1)

  • 快速幂算法:O(log p)

  • 每次乘法:O(500^2) = O(250000)

总时间复杂度为 O(250000 × log p),对于p ≤ 3100000,log p ≈ 22,总操作数约550万次,在合理范围内。

空间复杂度

使用固定大小的数组,空间复杂度为O(1)。

优化技巧

  1. 只保留必要位数:只计算和存储最后500位,大大减少计算量

  2. 快速幂算法:将指数运算从线性时间优化到对数时间

  3. 循环优化:在乘法中通过条件判断减少不必要的计算

常见问题解答

1. 为什么使用数组存储大数?

因为2^p的值可能达到数百万位,远超任何基本数据类型的表示范围。数组可以灵活地存储任意位数的大数。

2. 为什么数组的0索引表示个位?

这种存储方式(低位在前)便于进行进位处理,因为进位是向高位(索引增加的方向)进行的。

3. 快速幂算法为什么有效?

快速幂算法利用了指数的二进制表示性质。例如计算2^13:

  • 13的二进制是1101

  • 2^13 = 2^(8+4+1) = 2^8 × 2^4 × 2^1

  • 通过平方操作可以快速得到2^1, 2^2, 2^4, 2^8等幂次

4. 如何处理减法借位?

从个位开始减1,如果当前位不够减,就向高位借位(当前位加10,高位减1)。

测试样例

样例1:p = 1279

这是题目提供的一个测试样例,可以用来验证程序正确性。

样例2:p = 1000

较小值,便于手动验证。

总结

本题考察了大数运算和快速幂算法的应用。通过合理的算法设计和优化,可以在规定时间内完成计算。关键点包括:

  1. 利用数学公式直接计算位数,避免大数运算

  2. 使用快速幂算法高效计算大数幂

  3. 只保留必要的位数,减少计算量

  4. 合理的数据结构设计,便于处理进位和借位

http://www.dtcms.com/a/415345.html

相关文章:

  • 网站怎么管理维护wordpress主题模板制作教程
  • 做一个企业网站设计成都有哪些网站建设的公司
  • XCOSnTh单片机的IO口
  • 广东网站设计域名后面wordpress
  • 初识c语言————位运算符
  • 南充做网站的公司网络架构师证书
  • Appinventor笔记5-列表块
  • 天津做网站印标帝国手机网站怎么做
  • 单位网站建设有机房吗在线网站模板
  • 手写MyBatis第79弹:MyBatis二级缓存事务一致性:解决脏读与缓存一致性难题
  • TENGJUN-4极反向沉板耳机插座:JA05-BPD011-A;技术解析
  • Raft 算法深度解析:角色、选举、日志复制与分区处理优化
  • Linux进程(3)
  • 大型建设网站自己动手制作网站
  • 濮阳做公司网站青羊区城乡建设网站
  • 版式设计模板网站wordpress 获取文章
  • 操作系统页面置换算法FIFO——Belady异常与一个简单案例
  • 网站开发定制方案企业网店推广运营策略
  • 杭州设计企业网站高端公司游戏网站开发试验报告
  • React Native:使用vite创建react项目并熟悉react语法
  • LazyLLM 学习
  • 服饰 公司 网站建设新会网页制作公司
  • 做网站开发的营业执照电商货源网站大全
  • Redis 主从同步:原理、配置与实战优化
  • 什么是网站反链企业建设网站风险
  • 毕业设计开题报告网站开发深圳哪家网站设计比较好
  • 常用的Python项目管理工具
  • 网站建设设计技术方案模板linux 下启动 wordpress
  • 温建设文件发布在哪个网站做网站需要ui设计吗
  • 数字孪生背后的通信协议:MQTT、OPC UA选型指南