P1045 [NOIP2003 普及组] 麦森数

P1045 [NOIP2003 普及组] 麦森数

题目传送门

一、前导知识

高精度乘法

老师将高精乘高精,高精乘低精想办法合并成了一个模板

没错应该看的出来,高精度乘法其实就是一位一位去乘,然后按位存储在数组里面,思路差不多就是这样。

由于位数比较多,我们用字符串来进行输入,处理后按位存到整型数组中。

我们用下标来确定存数组的位置,从图中也可以看出a[i]b[j]就存在[i+j1]的位置上,然后每一位都进行累加(这里的累加是指同一位的累加,如a[2]b[1]a[1]b[2]是存在同一位上的,就是都在c[2]中进行累加),累加完毕后再处理进位,最后倒序输出就可以噜~

高精度乘高精度模板

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

快速幂

一、什么是快速幂

答:快速求出 ak 的结果!

二、快速幂的原理

答:快速幂算法的原理是通过将指数拆分成几个因数相乘的形式,来简化幂运算。在我们计算313 的时候,普通的幂运算算法需要计算13次,但是如果我们将它拆分成38+4+1 ,再进一步拆分成 只需要计算4次。嗯?哪来的4次?,别急,接着看。

这种拆分思想其实就是借鉴了二进制与十进制转换的算法思想,我们知道13的二进制是1101,可以知道:
13=1×23+1×22+0×21+1×20=8+4+1

原理就是利用位运算里的位移“>>”和按位与“&”运算,代码中k&1其实就是取k二进制的最低位,用来判断最低位是0还是1,再根据是0还是1决定乘不乘,不理解的话联系一下二进制转换的过程。
k>>=1其实就是将k的二进制向右移动一位,就这样位移、取最低位、位移、取最低位,这样循环计算,直到指数k0为止,整个过程和我们手动将二进制转换成十进制是非常相似的。

普通幂算法是需要循环指数次,也就是指数是多少就要循环计算多少次,而快速幂因为利用了位移运算,只需要算“指数二进制位的位数”次,对于13来说,二进制是1101,有4位,就只需要计算4次,快速幂算法时间复杂度是O(logn)级别,对于普通幂需要计算一百万次的来说,快速幂只需要计算6次,这是速度上质的飞跃,但是需要多注意溢出的问题。

三、简单粗暴快速幂(可用于结合高精度乘法)

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;

四、带取模的快速幂

// 快速幂 (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));

五、高精度结合快速幂

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、求2p1的位数

首先我们知道2p12p有着相同的位数,因为2的次方满足了最后一位不为零的要求,所以减一后位数并不会改变,那么我们可以直接求2p的位数。

怎么求位数呢?设k=2p,根据10n的位数为n+1,我们只要想办法把k=2p中的底数2改为10,指数加一就是位数了。由此想到用10的几次方来代替2,那么就不难想到10log102=2,这样便可以把k=2p中的2代换掉,变为$$\large k=(10{log_{10}2})p$$。
根据乘方的原理,将p乘进去,原式便可化为我们最终想要的形式

k=10log102p

所以:

=log102p+1

(提醒一下,C++cmath库自带log10()函数...)

2、保留500

#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;
}
posted @   糖豆爸爸  阅读(408)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2017-11-22 Mysql+ODBC+OpenLDAP
Live2D
点击右上角即可分享
微信分享提示