zoukankan      html  css  js  c++  java
  • 【题解】[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;
    }
    
  • 相关阅读:
    Git笔记
    排序学习LTR(1):排序算法的评价指标
    C++指针
    C++基础知识笔记
    Shell脚本--菜鸟教程笔记
    torch学习01-入门文档学习
    torch学习02-tensor学习
    torch学习0: 学习概览
    linux基础-用户创建及管理相关
    python-getattr() 函数 dir() 函数
  • 原文地址:https://www.cnblogs.com/ZhengkunJia/p/12305508.html
Copyright © 2011-2022 走看看