【题解】[P1045] 麦森数
题目
题目描述
形如2P-1**的素数称为麦森数,这时P一定也是个素数。但反过来不一定,即如果P是个素数,**2P-1
不一定也是素数。到1998年底,人们已找到了37个麦森数。最大的一个是P=3021377,它有909526位。麦森数有许多重要应用,它与完全数密切相关。
任务:从文件中输入P(1000<P<3100000),计算2^P-1的位数和最后500位数字(用十进制高精度数表示)
输入格式
文件中只包含一个整数P(1000<P<3100000)
输出格式
第一行:十进制高精度数2
P
−1的位数。
第2-11行:十进制高精度数2^p-1的最后500位数字。(每行输出50位,共输出10行,不足500位时高位补0)
不必验证2^P-1与P是否为素数。
输入输出样例
输入 #1
1279
输出 #1
386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087
分析
首先,肯定要用高精。
分析时间:
直接模拟的话,3100000 是 3*106,也就是要成3*106次2。
而麦森数位数为909526,是10^6级别。
所以用高精度每乘一次2,要运算10^6次(高精乘低精,时间复杂度为线性),
乘3*106次2,就要运算3*1012次,肯定要超时。
考虑用快速幂。
快速幂,是将3*106次乘2优化到log级别,也就是log2(3*106)大约是22。但是这样就要增加一个高精数组b[]来存2的幂,更新b[]数组也需要大量的时间。
这是更新b[]的代码
void get_cheng_b(){//函数的作用是将b[]平方
int h[len2+len2+1], x;
memset(h, 0, sizeof(h));
F(i,1,len2){
F(j,1,len2){
h[i+j-1] += b[i]*b[j];
x = h[i+j-1]/10;
h[i+j-1] %= 10;
int k = i+j;
while(x){
h[k] += x%10;
x /= 10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len2+len2]) len2 = len2+len2;
else len2 = len2+len2-1;
F(i,1,len2){
b[i] = h[i];
}
}
求2^P的函数:
void get_cheng_ch(){//多位乘多位
int h[len + len2 + 1];
memset(h, 0, sizeof(h));
int x=0;
F(i,1,len){
F(j,1,len2){
h[i+j-1]+=ch[i]*b[j];
x=h[i+j-1]/10;
h[i+j-1]%=10;
int k = i+j;
while(x){
h[k] += x%10;
x/=10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len+len2]){
len = len+len2;
}
else
len = len+len2-1;
F(i,1,len){
ch[i] = h[i];
}
}
void get_cheng_b(){
int h[len2+len2+1], x;
memset(h, 0, sizeof(h));
F(i,1,len2){
F(j,1,len2){
h[i+j-1] += b[i]*b[j];
x = h[i+j-1]/10;
h[i+j-1] %= 10;
int k = i+j;
while(x){
h[k] += x%10;
x /= 10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len2+len2]) len2 = len2+len2;
else len2 = len2+len2-1;
F(i,1,len2){
b[i] = h[i];
}
}
void get_jian(){
int i=1;
while(ch[i]==0){
ch[i] = 9;
i++;
}
ch[i]--;
if(ch[len] == 0 && i==len) len--;
}
void get_mi(){
b[1]=2;
ch[1]=1;
while(p){
if(p%2){
get_cheng_ch();//ch[]乘b[]
}
get_cheng_b();//b[]平方
p/=2;
}
get_jian();
}
2的幂,最大更新到2P(比如P=64时,264=2^64),
也就是说,b[]的长度最大是2(3*106),不也还是10^6吗?
每调用一次get_cheng_b()函数,就要运行(106)2次(高精乘高精,时间复杂度是平方级别),
而while循环22次每次调用一个get_cheng_b()和一个get_cheng_ch(),一共要运算22*[(106)2+10^6]次,时间是无法接受的。
咋办?
但是仔细读题,发现题目只让求后500位。也就是说b[]和ch[]只用寸500位的数据
所以时间问题迎刃而解(其实仔细想想问题的含义就不会有时间问题了)。
位数怎么搞?
是2P-1的位数,与2P的位数一样。
注意到10^m的位数是m+1。
我们将2^P转化为以10为底,这样次数加一就是位数。
设次数为x,10x=2P,
x=log10(2^P)=[log10(2)]*P
输出x+1即可。
还有两个问题:
1.减一的退位问题
发现2的幂末位不可能为0,所以不考虑退位,jian()函数可以去掉。
2.前导零问题
像我这样直接开一个500位的数组,全部初始为0,如果总位数小于500,那前面的0是没动的,直接输出数组元素,不用考虑前导零。
代码改后是这样:
void get_cheng_ch(){//多位乘多位
int h[len + len2 + 1];
memset(h, 0, sizeof(h));
int x=0;
F(i,1,len){
F(j,1,len2){
h[i+j-1]+=ch[i]*b[j];
x=h[i+j-1]/10;
h[i+j-1]%=10;
int k = i+j;
while(x){
h[k] += x%10;
x/=10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len+len2]){
len = min(510,len+len2);
}
else
len = min(510,len+len2-1);
F(i,1,len){
ch[i] = h[i];
}
}
void get_cheng_b(){
int h[len2+len2+1], x;
memset(h, 0, sizeof(h));
F(i,1,len2){
F(j,1,len2){
h[i+j-1] += b[i]*b[j];
x = h[i+j-1]/10;
h[i+j-1] %= 10;
int k = i+j;
while(x){
h[k] += x%10;
x /= 10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len2+len2]) len2 = min(510,len2+len2);
else len2 = min(510,len2+len2-1);
F(i,1,len2){
b[i] = h[i];
}
}
void get_mi(){
b[1]=2;
ch[1]=1;
while(p){
if(p%2){
get_cheng_ch();//ch[]乘b[]
}
if(p/2==0) break;
get_cheng_b();//b[]平方
p/=2;
}
}
进一步优化
我从洛谷题解看到,有人两部优化了高精算法:
用一个数组元素存十位数字,只用长度为50的数组就存下了500位;然后把乘2变成乘2^20(这样longlong是完全接受的)。
比模拟乘2快不少,在500位、P=3*10^6数据下时间复杂度与快速幂相当(再加上快速幂岂不是天下无敌?)。
但这样会带来进位的变动和前导零的麻烦。
我就不写了,copy下地址
My AC code:
#include <cmath>
#include <cstdio>
#include <string>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define F(i,a,b) for(int i=a;i<=b;i++)
#define UF(i,a,b) for(int i=a;i>=b;i--)
#define N 1000010
#include <time.h>
using namespace std;
int ch[N], p, len=1,b[N], a=2, len2=1;
void get_cheng_ch(){//多位乘多位
int h[len + len2 + 1];
memset(h, 0, sizeof(h));
int x=0;
F(i,1,len){
F(j,1,len2){
h[i+j-1]+=ch[i]*b[j];
x=h[i+j-1]/10;
h[i+j-1]%=10;
int k = i+j;
while(x){
h[k] += x%10;
x/=10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len+len2]){
len = min(510,len+len2);
}
else
len = min(510,len+len2-1);
F(i,1,len){
ch[i] = h[i];
}
}
void get_cheng_b(){
int h[len2+len2+1], x;
memset(h, 0, sizeof(h));
F(i,1,len2){
F(j,1,len2){
h[i+j-1] += b[i]*b[j];
x = h[i+j-1]/10;
h[i+j-1] %= 10;
int k = i+j;
while(x){
h[k] += x%10;
x /= 10;
x += h[k]/10;
h[k] %= 10;
k++;
}
}
}
if(h[len2+len2]) len2 = min(510,len2+len2);
else len2 = min(510,len2+len2-1);
F(i,1,len2){
b[i] = h[i];
}
}
void get_jian(){//2^p一定不以0结尾,所以不用这个函数
int i=1;
while(ch[i]==0){
ch[i] = 9;
i++;
}
ch[i]--;
if(ch[len] == 0 && i==len) len--;
}
void print(int a[], int len){
cout<<"len2="<<len<<endl;
UF(i,len,1)
cout<<a[i];
cout << endl;
}
void get_mi(){
b[1]=2;
ch[1]=1;
while(p){
if(p%2){
get_cheng_ch();//ch[]乘b[]
}
if(p/2==0) break;
get_cheng_b();//b[]平方
p/=2;
// print(b, len2);
}
// get_jian();
}
int main()
{
cin >> p;
double anslen;
anslen=log10(2)*p+1;
cout<<(int)anslen<<endl;
get_mi();
// cout << len << endl;
UF(i,500,2){
cout << ch[i];
if((i-1)%50==0) cout << endl;
}
cout<<ch[1]-1<<endl;
// printf("%lf",(double)clock()/CLOCKS_PER_SEC);
return 0;
}
【题解】[P1045] 麦森数的更多相关文章
- P1045麦森数
P1045麦森数 #include<iostream> #include <cmath> #include <cstring> const int maxn = 1 ...
- 洛谷试炼场-简单数学问题-P1045 麦森数-高精度快速幂
洛谷试炼场-简单数学问题 B--P1045 麦森数 Description 形如2^P−1的素数称为麦森数,这时P一定也是个素数.但反过来不一定,即如果PP是个素数,2^P-1 不一定也是素数.到19 ...
- 洛谷 P1045 麦森数
题目描述 形如2^{P}-1的素数称为麦森数,这时P一定也是个素数.但反过来不一定,即如果P是个素数,2^{P}-1不一定也是素数.到1998年底,人们已找到了37个麦森数.最大的一个是P=30213 ...
- 洛谷P1045 麦森数
题目描述 形如2^{P}-12 P −1的素数称为麦森数,这时PP一定也是个素数.但反过来不一定,即如果PP是个素数,2^{P}-12 P −1不一定也是素数.到1998年底,人们已找 ...
- 洛谷 P1045 麦森数 (快速幂+高精度+算位数骚操作)
这道题太精彩了! 我一开始想直接一波暴力算,然后叫上去只有50分,50分超时 然后我改成万位制提高运算效率,还是只有50分 然后我丧心病狂开long long用10的10次方作为一位,也就是100亿进 ...
- NOIP2003 普及组 洛谷P1045 麦森数 (快速幂+高精度)
有两个问题:求位数和求后500位的数. 求位数:最后减去1对答案的位数是不影响的,就是求2p的位数,直接有公式log10(2)*p+1; 求后500位的数:容易想到快速幂和高精度: 1 #includ ...
- P1045 麦森数
别问我为什么要写水题 #include <iostream> #include <cstdio> #include <cstring> #include <a ...
- P1045 [NOIP2003 普及组] 麦森数
题目描述 形如2^P−1的素数称为麦森数,这时P一定也是个素数.但反过来不一定,即如果P是个素数,2^P−1不一定也是素数. 到1998年底,人们已找到了37个麦森数.最大的一个是P=3021377, ...
- 【03NOIP普及组】麦森数(信息学奥赛一本通 1925)(洛谷 1045)
[题目描述] 形如2P-1的素数称为麦森数,这时P一定也是个素数.但反过来不一定,即如果P是个素数,2P-1不一定也是素数.到1998年底,人们已找到了37个麦森数.最大的一个是P=3021377,它 ...
随机推荐
- ACM北大暑期课培训第二天
今天继续讲的动态规划 ... 补充几个要点: 1. 善于利用滚动数组(可减少内存,用法与计算方向有关) 2.升维 3.可利用一些数据结构等方法使代码更优 (比如优先队列) 4.一般看到数值小的 (十 ...
- MapGIS文件如何压缩存盘
经过多次编辑修改的MapGIS数据,含有大量逻辑上已删除的节点或图元,数据冗余复杂, 在转换过程前应注意一定要采用压缩存盘方式处理,目的是确保编辑状态已删除的数据真正从物理存储层删除,以确保数据的精简 ...
- docker启动报错 (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport
今天修改完docker宿主机的防火墙文件 vim /etc/sysconfig/iptables 停止容器再启动时 报如下错误 (iptables failed: iptables --wait -t ...
- Java 中的foreach(增强for循环)
foreach概述 增强for循环:底层使用的是送代器,使用for循环的格式,简化了送代器的书写,foreach是JDK1.5之后出现的新特性 使用增强for循环 遍历集合 /** * 遍历集合 * ...
- 【大道至简】NetCore3.1快速开发框架一:搭建框架
这一章,我们直接创建NetCore3.1的项目 主要分为1个Api项目,和几个类库 解释: 项目——FytSoa.Api:提供前端接口的Api项目 类库——FytSoa.Core:包含了数据库操作类和 ...
- Linux.vim.多行复制、删除、剪切
复制: //单行复制+粘贴 yy + p:复制光标所处当前行, 敲p粘贴在光标处. //多行复制+粘贴 n + yy + p:复制光标所在行起以下n行(含当前行), 敲yy复制光标所处当前行, 敲p粘 ...
- 最短路径-Dijkstra+Floyd+Spfa
Dijkstra算法: Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径.主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止.Dijkstra ...
- 获取PHP类的所有属性和所有方法,可通过反射机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php class Class1{ public $var1 = 'v ...
- 深入了解Zookeeper
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等.Zookeeper是hadoop的一个子项目,其 ...
- spring中创建bean对象的三种方式以及作用范围
时间:2020/02/02 一.在spring的xml配置文件中创建bean对象的三种方式: 1.使用默认构造函数创建.在spring的配置文件中使用bean标签,配以id和class属性之后,且没有 ...