数论 —— 快速幂

【概述】

快速幂就是利用二进制的性质来快速计算底数的 n 次幂,时间复杂度为 O(log₂N), 与朴素的 O(N) 相比效率有了极大的提高。

但有时指数十分大,利用高精来写可能会超时,此时就需要十进制快速幂。

【求位数公式】

有时仅要求求 2 的 n 次幂的位数,这里有一个神奇的求位数公式:floor(log(2)/log(10)*n+1)

double res=n*log10(2)+1;
cout<<int(res)<<endl;

【二进制快速幂】

以求 a 的 b 次方为例

将 b 转换成二进制数,该二进制数第 i 位的权为: 

例如:

11 的二进制是 1011,即:11 = 2³×1 + 2²×0 + 2¹×1 + 2º×1

因此,我们将计算 a¹¹ 转化为计算 

递归公式:

x^y=\left\{\begin{matrix}1,y=0 \\ x*x^{y-1},y=2*n+1 \\ (x^2)^{y/2},y=2*n \end{matrix}\right.

可借助位运算来实现:

  1. b & 1 //取 b 二进制的最低位,判断和 1 是否相同,相同返回 1,否则返回 0,用于判断奇偶
  2. b>>1 //把 b 的二进制右移一位,即去掉其二进制位的最低位
int quickPow(int a,int b){
    int res=1;
    while(b){
        if(b&1)
            res*=a;
        a*=a;
        b>>=1;
    }
    return res;
}

【二进制快速幂取模】

所谓二进制快速幂取模,就是计算给出 a,b,m 三个数,快速计算 a^{b} mod\: m 的值

分析:以求 3^{89} mod\; 7 为例

3^{1}\equiv 3(mod\: \, 7)

3^{2}\equiv 3^{2}(mod\: \, 7)\equiv 2(mod\: \, 7)

3^{4}\equiv (3^{2})^{2}(mod\: \, 7)\equiv 4^{2}(mod\: \, 7)\equiv 2

3^{8}\equiv (3^{8})^{2}(mod\: \, 7)\equiv 2^{2}(mod\: \, 7)\equiv 4

3^{32}\equiv (3^{16})^{2}(mod\: \, 7)\equiv 4^{2}(mod\: \, 7)\equiv 2

3^{64}\equiv (3^{32})^{2}(mod\: \, 7)\equiv 2^{2}(mod\: \, 7)\equiv 4

3^{89}\equiv 3^{64}*3^{16}*3^{8}*3^{1}(mod\: \, 7)\equiv 4*4*2*3(mod\: \, 7)\equiv 96(mod\: \, 7)\equiv 5(mod\: \, 7)

因此,可递归的对 n 进行二进制分解,在进行快速幂运算的同时进行求模。

int powMod(int a, int b, int m){
    int res=1;
    while(b){
        if(b&1)
            res=(res*a)%m;
        a=(a*a)%m;
        b>>=1;
    }
    return res;
}

【快速乘法的二进制快速幂取模】

当数非常大时,计算 (a*b)%m 使用 long long 也有可能爆精度,此时需要利用快速乘法,将转乘法为加法,在模拟的同时不断求模

LL multMod(LL a,LL b,LL m){//res=(a*b)%m
    a%=m;
    b%=m;
    LL res=0;
    while(b){
        if(b&1)
            res=(res+a)%m;
        a=(a<<=1)%m;
        b>>=1;
    }
    return res%m;
}
LL powMod(LL a, LL b, LL m){//res=(a^b)%m
    LL res=1;
    LL k=a;
    while(b){
        if((b&1))
            res=multMod(res,k,m)%m;
        k=Mult_Mod(k,k,m)%m;
        b>>=1;
    }
    return res%m;
}

【十进制快速幂】

考虑到指数十分大的情况,例如:k=10^{100000},此时用 python 或 Java 大数写二进制快速幂会超时,这个时候就需要用十进制快速幂来解决。

十进制快速幂与二进制快速幂没有本质区别,只是拆分指数的方法一个是以十进制,一个是以二进制。

例如:3^{443}=(3^1)^{3}*(3^{10})^4*(3^{100})^4

char b[100007];
LL tenthPow(LL a,LL len,LL mod) {
    LL res=1;
    while(len>=0){
        LL cnt=b[len]-'0';
        LL cur=a;
        for(int i=1; i<=cnt; i++)
            res=res*a%mod;
        for(int i=1; i<10; i++)
            cur=cur*a%mod;

        //进位
        a=cur;
        res%=mod;
        len--;
    }
    return res;
}
int main(){
    LL a,mod;
    scanf("%lld",&a);
    scanf("%s",b);//字符串读入指数
    scanf("%lld",&mod);

    LL len=strlen(b);
    LL res=tenthPow(a,len-1,mod);
    printf("%lld^%s %% %lld = %lld",a,b,mod,res);
    return 0;
}

【例题】

  • 麦森数(洛谷-P1045)(快速幂+高精度)点击这里
  • 取余运算||快速幂(洛谷-P1226)(快速幂取模)点击这里
  • Rightmost Digit(HDU-1061)(快速幂取模)点击这里
  • Leading and Trailing(LightOJ-1282)(快速幂取模+快速幂取高位)点击这里
  • Pupu(HDU-3003)(公式推导+快速幂取模)点击这里
  • 欧拉定理(洛谷-P5091)(十进制快速幂)点击这里
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值