CSP历年复赛题-P1045 [NOIP2003 普及组] 麦森数
原题链接:https://www.luogu.com.cn/problem/P1045
题意解读:
要计算2p- 1的位数和最后500位,实际上只需要计算2p,两者位数一致,前者比后者个位减1即可,且个位肯定不会是0,比较容易处理。
解题思路:
一、朴素做法
如果直接采用高精度乘法计算2p,p最大3.1*106, 高精度所用数组最长大概9*105,一共最多计算3.1*106*9*105,超时,需要考虑优化。
1、对于位数的计算
我们知道,一个十进制数的位数很好计算,假设一个十进制数为10k,则位数为k+1;
那么,如果2p能转化为10k形式,问题就得以解决;
因为2 = 10log102,所以2p = (10log102)p = 10log102 * p
进而可得到位数:log102 * p + 1,取整。
log10()为系统函数。
2、对于后500位的计算
由于数据位数可高达9*105位,如果保存完整的数据进行高精度乘法,势必超时
但题目要求只取后500位,相当于结果取模10500
因此,乘法结果取模,相当于对乘法的每个因子取模后再相乘
这样,在高精度乘法的时候,每次只用保留最低500位即可,其余高位的数据可以舍弃不必计算
另外,如果每次都乘以2,一共最大要乘3.1*106次,每次高精度取500位,最大复杂度大概3.1*106*500,同样会超时
而如果每次都乘以230,则最多乘的次数大概100000次,每次高精度取500为,复杂度估算在5 * 107
所以,需要先设n = p / 30,则有n个230相乘,再看m = p % 30,结果还要乘上2m
为了减少计算时间,可以提前预处理出21、22、23...230的结果
注意为什么不取到231?因为int最大是231-1
100分代码:
#include <bits/stdc++.h>
using namespace std;
int pow2[31];
//初始化2的1、2...30次方
void init()
{
int ans = 1;
for(int i = 1; i <= 30; i++)
{
ans *= 2;
pow2[i] = ans;
}
}
//高精度*低精度,每次只取前500位,即模10^500,起到优化时间复杂度的效果
vector<int> mul(vector<int> &a, int b)
{
vector<int> result;
long long t = 0;
for(int i = 0; i < a.size() && result.size() < 500; i++)
{
t += (long long)a[i] * b;
result.push_back(t % 10);
t /= 10;
}
while(t && result.size() < 500)
{
result.push_back(t % 10);
t /= 10;
}
return result;
}
int main()
{
init();
int p;
cin >> p;
cout << (int)(log10(2) * p + 1) << endl;
int n = p / 30; //p有多少个30,要计算n次2^30相乘
int m = p % 30; //如果m不为0,计算n次2^30相乘后,再乘以2^m
vector<int> ans(1, 1);
for(int i = 1; i <= n; i++) ans = mul(ans, pow2[30]); //计算n次2^30相乘
ans = mul(ans, pow2[m]); //再乘以2^m
ans[0] -= 1; //个位减1
while(ans.size() < 500) ans.push_back(0); //不足500位,高位补0
for(int i = ans.size() - 1, j = 1; i >= 0; i--, j++)
{
cout << ans[i];
if(j % 50 == 0) cout << endl;
}
return 0;
}
二、快速幂
如果了解快速幂,可以很容易解决时间复杂度的问题,只需要在快速幂中进行高精度 * 高精度即可,具体请参考代码
100分代码:
#include <bits/stdc++.h>
using namespace std;
int p;
//高精度*高精度,取前500位
vector<int> mul(vector<int> &a, vector<int> &b)
{
vector<int> res(501, 0);
for(int i = 0; i < a.size(); i++)
{
for(int j = 0; j < b.size(); j++)
{
if(i + j > 500) break;
res[i + j] += a[i] * b[j];
res[i + j + 1] += res[i + j] / 10;
res[i + j] %= 10;
}
}
return res;
}
int main()
{
cin >> p;
int ans1 = log10(2) * p + 1;
cout << ans1 << endl;
vector<int> a(1, 2);
vector<int> ans2(1, 1);
//快速幂计算a^p%500
while(p)
{
if(p & 1) ans2 = mul(ans2, a);
a = mul(a, a);
p = p >> 1;
}
ans2[0] -= 1; //个位减1
for(int i = 499, j = 1; i >= 0; i--, j++)
{
cout << ans2[i];
if(j % 50 == 0) cout << endl;
}
}