You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

P1045 [NOIP2003 普及组] 麦森数

题目传送门

一、前导知识

高精度乘法

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

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

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

我们用下标来确定存数组的位置,从图中也可以看出a[i]*b[j]就存在[i + j - 1]的位置上,然后每一位都进行累加(这里的累加是指同一位的累加,如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;
}

快速幂

一、什么是快速幂

答:快速求出 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

原理就是利用位运算里的位移“>>”和按位与“&”运算,代码中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、求2^p-1的位数

首先我们知道\large 2^p-1\large 2^p有着相同的位数,因为2的次方满足了最后一位不为零的要求,所以减一后位数并不会改变,那么我们可以直接求\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

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