Files
python/TangDou/LuoGuBook/P1045.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

214 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
```