|
|
|
|
P1045 [NOIP2003 普及组] 麦森数
|
|
|
|
|
|
|
|
|
|
[题目传送门](https://www.luogu.com.cn/problem/P1045)
|
|
|
|
|
|
|
|
|
|
## 一、前导知识
|
|
|
|
|
|
|
|
|
|
### 高精度乘法
|
|
|
|
|
老师将高精乘高精,高精乘低精想办法合并成了一个模板
|
|
|
|
|
<center><img src='https://img-blog.csdnimg.cn/img_convert/f1972536042276c9d6106df899381c7f.png'></center>
|
|
|
|
|
|
|
|
|
|
没错应该看的出来,高精度乘法其实就是一位一位去乘,然后按位存储在数组里面,思路差不多就是这样。
|
|
|
|
|
|
|
|
|
|
由于位数比较多,我们用字符串来进行输入,处理后按位存到整型数组中。
|
|
|
|
|
|
|
|
|
|
我们用下标来确定存数组的位置,从图中也可以看出$a[i]*b[j]$就存在$[i + j - 1]$的位置上,然后每一位都进行累加(这里的累加是指同一位的累加,如$a[2]*b[1]$和$a[1]*b[2]$是存在同一位上的,就是都在$c[2]$中进行累加),累加完毕后再处理进位,最后倒序输出就可以噜~
|
|
|
|
|
|
|
|
|
|
**高精度乘高精度模板**
|
|
|
|
|
```c++
|
|
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
const int N = 1e5 + 10;
|
|
|
|
|
int a[N], al;
|
|
|
|
|
int b[N], bl;
|
|
|
|
|
//高精度乘高精度模板
|
|
|
|
|
void mul(int a[], int &al, int b[], int bl) {
|
|
|
|
|
int c[N] = {0}, cl = al + bl;
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i <= al; i++)
|
|
|
|
|
for (int j = 1; j <= bl; j++)
|
|
|
|
|
c[i + j - 1] += a[i] * b[j];
|
|
|
|
|
|
|
|
|
|
int t = 0;
|
|
|
|
|
for (int i = 1; i <= al + bl; i++) {
|
|
|
|
|
t += c[i];
|
|
|
|
|
c[i] = t % 10;
|
|
|
|
|
t /= 10;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//前导0
|
|
|
|
|
while (cl > 1 && c[cl] == 0) cl--;
|
|
|
|
|
|
|
|
|
|
//将C数组复制回A数组
|
|
|
|
|
memcpy(a, c, sizeof c);
|
|
|
|
|
al = cl;
|
|
|
|
|
}
|
|
|
|
|
int main() {
|
|
|
|
|
string x, y;
|
|
|
|
|
cin >> x >> y;
|
|
|
|
|
for (int i = x.size() - 1; i >= 0; i--) a[++al] = x[i] - '0';
|
|
|
|
|
for (int i = y.size() - 1; i >= 0; i--) b[++bl] = y[i] - '0';
|
|
|
|
|
|
|
|
|
|
mul(a, al, b, bl);
|
|
|
|
|
for (int i = al; i; i--) printf("%d", a[i]);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 快速幂
|
|
|
|
|
#### 一、什么是快速幂
|
|
|
|
|
答:快速求出 $a^k$ 的结果!
|
|
|
|
|
|
|
|
|
|
#### 二、快速幂的原理
|
|
|
|
|
答:快速幂算法的原理是通过将指数拆分成几个因数相乘的形式,来简化幂运算。在我们计算$3^{13}$ 的时候,普通的幂运算算法需要计算$13$次,但是如果我们将它拆分成$3^{8+4+1}$ ,再进一步拆分成 只需要计算$4$次。嗯?哪来的$4$次?,别急,接着看。
|
|
|
|
|
|
|
|
|
|
这种拆分思想其实就是借鉴了二进制与十进制转换的算法思想,我们知道$13$的二进制是$1101$,可以知道:
|
|
|
|
|
$13=1×2^3 + 1×2^2 + 0×2^1 + 1×2^0 = 8 + 4 + 1$
|
|
|
|
|
|
|
|
|
|
原理就是利用位运算里的位移“>>”和按位与“&”运算,代码中<font color='red'>$k\&1$其实就是取$k$二进制的最低位</font>,用来判断最低位是$0$还是$1$,再根据是$0$还是$1$决定乘不乘,不理解的话联系一下二进制转换的过程。
|
|
|
|
|
$k >>= 1$其实就是将k的二进制向右移动一位,就这样位移、取最低位、位移、取最低位,这样循环计算,直到指数$k$为$0$为止,整个过程和我们手动将二进制转换成十进制是非常相似的。
|
|
|
|
|
|
|
|
|
|
普通幂算法是需要循环指数次,也就是指数是多少就要循环计算多少次,而快速幂因为利用了位移运算,只需要算“**指数二进制位的位数**”次,对于$13$来说,二进制是$1101$,有$4$位,就只需要计算$4$次,快速幂算法时间复杂度是$O(logn)$级别,对于普通幂需要计算一百万次的来说,快速幂只需要计算$6$次,这是速度上质的飞跃,但是需要多注意溢出的问题。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 三、简单粗暴快速幂(可用于结合高精度乘法)
|
|
|
|
|
```c++
|
|
|
|
|
int qmi(int a, int k) {
|
|
|
|
|
int res = 1;
|
|
|
|
|
while (k) {
|
|
|
|
|
if (k & 1) res = res * a;
|
|
|
|
|
k >>= 1;
|
|
|
|
|
a = (LL) a * a;
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cout<<qmi(3,4)<<endl;
|
|
|
|
|
```
|
|
|
|
|
#### 四、带取模的快速幂
|
|
|
|
|
```c++
|
|
|
|
|
// 快速幂 (a^k)%p
|
|
|
|
|
int qmi(int a, int k, int p) {
|
|
|
|
|
int res = 1; //答案
|
|
|
|
|
while (k) { //一路让k变小直到为0停止
|
|
|
|
|
if (k & 1) res = (LL) res * a % p; //如果k的个位是1的话
|
|
|
|
|
k >>= 1; //右移一位
|
|
|
|
|
a = (LL) a * a % p; //1-2-4-8-16,就是每进一位,是把a=a*a,注意使用long long 防止在乘积过程中爆了int
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qmi(a, k, p));
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 五、高精度结合快速幂
|
|
|
|
|
```c++
|
|
|
|
|
int a[N], al;
|
|
|
|
|
int b[N], bl;
|
|
|
|
|
|
|
|
|
|
void mul(int a[], int &al, int b[], int &bl) {
|
|
|
|
|
int c[N] = {0}, cl = al + bl;
|
|
|
|
|
for (int i = 1; i <= al; i++)
|
|
|
|
|
for (int j = 1; j <= bl; j++)
|
|
|
|
|
c[i + j - 1] += a[i] * b[j];
|
|
|
|
|
|
|
|
|
|
int t = 0;
|
|
|
|
|
for (int i = 1; i <= al + bl; i++) {
|
|
|
|
|
t += c[i];
|
|
|
|
|
c[i] = t % 10;
|
|
|
|
|
t /= 10;
|
|
|
|
|
}
|
|
|
|
|
memcpy(a, c, sizeof c);
|
|
|
|
|
al = cl;
|
|
|
|
|
//前导0
|
|
|
|
|
while (al > 1 && a[al] == 0) al--;
|
|
|
|
|
}
|
|
|
|
|
//快速幂+高精度 x^y
|
|
|
|
|
void qmi(int x, int y) {
|
|
|
|
|
a[++al] = 1, b[++bl] = x;
|
|
|
|
|
while (y) {
|
|
|
|
|
if (y & 1) mul(a, al, b, bl);
|
|
|
|
|
y >>= 1;
|
|
|
|
|
mul(b, bl, b, bl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 二、本题思路
|
|
|
|
|
这道题可以分为两个模块,第一个模块为求的位数,第二个模块为求的后$500$位(不足补零)。
|
|
|
|
|
|
|
|
|
|
### 1、求$2^p-1$的位数
|
|
|
|
|
首先我们知道$\large 2^p-1$与$\large 2^p$有着相同的位数,因为$2$的次方满足了<font color='blue' size=4><b>最后一位不为零</b></font>的要求,所以<font color='red' size=4><b>减一后位数并不会改变</b></font>,那么我们可以直接求$\large 2^p$的位数。
|
|
|
|
|
|
|
|
|
|
怎么求位数呢?设$\large k=2^p$,根据$10^n$的位数为$n+1$,我们只要想办法把$k=2^p$中的底数$2$改为$10$,指数加一就是位数了。由此想到用$10$的几次方来代替$2$,那么就不难想到$10^{log_{10}2}=2$,这样便可以把$k=2^p$中的$2$代换掉,变为$$\large k=(10^{log_{10}2})^p$$。
|
|
|
|
|
根据乘方的原理,将$p$乘进去,原式便可化为我们最终想要的形式
|
|
|
|
|
|
|
|
|
|
$$\large k=10^{log_{10}2*p} $$
|
|
|
|
|
|
|
|
|
|
所以:
|
|
|
|
|
|
|
|
|
|
$\large 位数=log_{10}2*p+1$
|
|
|
|
|
|
|
|
|
|
(提醒一下,$C++$中$cmath$库自带$log10()$函数...)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 2、保留$500$位
|
|
|
|
|
```c++
|
|
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
const int N = 1e5 + 10;
|
|
|
|
|
int a[N], al;
|
|
|
|
|
int b[N], bl;
|
|
|
|
|
|
|
|
|
|
void mul(int a[], int &al, int b[], int &bl) {
|
|
|
|
|
int c[N] = {0}, cl = al + bl;
|
|
|
|
|
for (int i = 1; i <= al; i++)
|
|
|
|
|
for (int j = 1; j <= bl; j++)
|
|
|
|
|
c[i + j - 1] += a[i] * b[j];
|
|
|
|
|
|
|
|
|
|
int t = 0;
|
|
|
|
|
for (int i = 1; i <= al + bl; i++) {
|
|
|
|
|
t += c[i];
|
|
|
|
|
c[i] = t % 10;
|
|
|
|
|
t /= 10;
|
|
|
|
|
}
|
|
|
|
|
//将C数组复制回A数组
|
|
|
|
|
memcpy(a, c, sizeof c);
|
|
|
|
|
al = min(500, cl); //只保留最大500个长度,不加这句话,会有3个点TLE掉
|
|
|
|
|
//前导0
|
|
|
|
|
while (al > 1 && a[al] == 0) al--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//快速幂+高精度 x^y
|
|
|
|
|
void qmi(int x, int y) {
|
|
|
|
|
a[++al] = 1, b[++bl] = x;
|
|
|
|
|
while (y) {
|
|
|
|
|
if (y & 1) mul(a, al, b, bl);
|
|
|
|
|
y >>= 1;
|
|
|
|
|
mul(b, bl, b, bl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
//计算 2^y-1的值
|
|
|
|
|
int y;
|
|
|
|
|
cin >> y;
|
|
|
|
|
//利用快速幂,计算2^y
|
|
|
|
|
qmi(2, y);
|
|
|
|
|
|
|
|
|
|
//最后一位减去一个1,因为2^k最后一位肯定不是0,所以减1不会产生借位,直接减去即可!
|
|
|
|
|
a[1]--;
|
|
|
|
|
|
|
|
|
|
//一共多少位
|
|
|
|
|
printf("%d\n", (int)(y * log10(2) + 1));
|
|
|
|
|
|
|
|
|
|
for (int i = 500; i; i--) {
|
|
|
|
|
printf("%d", a[i]);
|
|
|
|
|
//该换行了,就是到了第二行的行首
|
|
|
|
|
if ((i - 1) % 50 == 0) puts("");
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|