算法入门学习--快速幂

一、快速幂

所谓快速幂,就是一种快速求一个数的n次幂的算法,我们常用的求幂的操作是pow(x,n)函数,但是在处理大指数的幂运算时,快速幂算法的性能优势会更加明显,能显著减少计算时间。

二、算法实现

这里先从一个简单的问题进行引入,在不适用pow函数的情况下,我们如何快速的进行计算一个数a的b次幂对m取模呢。我们常见的做法就是直接暴力,就像如下代码,时间复杂度显而易见是O(b)。

int noramlPow(int a,int b,int m){
	int ans=1;
	for(int i=1;i<=b;i++){
		ans=ans*a%m;
	} 
	return ans;
}

 在指数特别大的情况下,朴素算法是非常耗时的,所以对于大指数的幂运算,我们对上述运算进行了优化,就是我们的快速幂算法。快速幂算法是一种基于二分的思想的算法,因此通常也称为二分幂。

快速幂主要的实现思想是:

①当b是奇数时,a^{b}=a^{b-1}*a

②当b是偶数时,a^{b}=a^{\frac{b}{2}}*a^{\frac{b}{2}} 。

int fastPow(int a,int b,int m){
	if(b==0) return 1;
	else if(b%2==1) return a%m*fastPow(a,b-1,m)%m;
	else {
		int mul=fastPow(a,b/2,m)%m;
		return mul*mul%m;
	}
}

为了加快执行速度,b%2==1的操作也可以用位运算b&1==1来替代。下列是快速幂二进制且递推实现代码:

//快速幂的递推写法,以这个为模板较好,因为大部分题目用递推写法性能更佳
int fastPow(int a,int b,int m){
	int ans=1;
	while(b>0){
		if(b&1) ans=ans*a%m;
		a=a*a%m;
		b>>=1;
	}
	return ans;
} 

三、常见例题

这里依旧摘录《算法笔记》,之后遇到比较好的题也会加进来~

 快速幂--板子 ​​​​​​

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'

int fastPow(int a,int b){
    int ans=1;
    while(b>0){
        if(b&1) ans=ans*a;
        a=a*a;
        b>>=1;
    }
    return ans;
}

signed main(){
    int a,b;
    cin>>a>>b;
    cout<<fastPow(a,b)<<endl;
}

 快速幂--模板二

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'

int fastPow(int a,int b,int m){
    int ans=1;
    while(b>0){
        if(b&1) ans=ans*a%m;  //即if(b%2==1)
        a=a*a%m;
        b>>=1;  //即b/=2;
    }
    return ans;
}
/*
int fastPow(int a,int b,int m){
    int ans=1;
    while(b>0){
        if(b%2==1) ans=ans*a%m;
        a=a*a%m;
        b/=2;
    }
    return ans;
}
*/
signed main(){
    int a,b,m;
    cin>>a>>b>>m;
    cout<<fastPow(a,b,m)<<endl;
}

 四、真题

这里是一道今年的郑州轻工业大学的校赛题:I-#define int bigint_筑梯杯郑州轻工业大学第十七届程序设计大赛(同步赛)

#define int bigint

时间限制:C/C++/Rust/Pascal 1秒,其他语言2秒
空间限制:C/C++/Rust/Pascal 512 M,其他语言1024 M
64bit IO Format: %lld

题目描述 

给定一个大整数 aa,它以质因数分解的形式表示为 a=p1e1×p2e2×⋯×pnena=p1​e1​×p2​e2​×⋯×pn​en​。

其中,p1,p2,⋯ ,pnp1​,p2​,⋯,pn​ 是质数,e1,e2,⋯ ,ene1​,e2​,⋯,en​ 是对应的指数。

再给定一个正整数 bb,请你计算 ⌊ab⌋⌊ba​⌋,即 aa 除以 bb 向下取整的结果。

由于结果可能很大,请你输出结果对 109+7109+7 取模后的值。

输入描述:

第一行包含一个整数 nn (1≤n≤105)(1≤n≤105),表示 aa 的质因数分解中的项数。
接下来的 nn 行,每行包含两个整数 pi,eipi​,ei​ (2≤pi≤999 983, 1≤ei≤109)(2≤pi​≤999983,1≤ei​≤109),表示 aa 的一个质因数及其指数。
数据保证 p1,p2,⋯ ,pnp1​,p2​,⋯,pn​ 是质数且互不相等。
最后一行包含一个正整数 bb (1≤b≤109)(1≤b≤109)。

输出描述:

输出一个整数,表示 ⌊ab⌋⌊ba​⌋ 对 109+7109+7 取模的结果。

示例1

输入

4
2 10
3 6
5 3
11 2
7

输出

612964564

备注:

在样例中:
a=210×36×53×112=11 290 752 000a=210×36×53×112=11290752000
⌊a/b⌋=⌊1 612 964 571.428 571 5⌋=1 612 964 571⌊b/a​⌋=⌊1612964571.4285715⌋=1612964571
1 612 964 571 mod 1 000 000 007=612 964 5641612964571mod1000000007=612964564

 本题很有难度,主要用到了快速幂+乘法逆元+简单公式推导

简单公式推导如下:

这样就可以将题目拆解为

 

 然后就是两个快速幂+一个乘法逆元了。

AC代码:

快速幂+扩展欧几里得计算乘法逆元 (由于题目给的1e9+7是质数)

哭了,这题数据量太大,不能用快速幂的递归写法,只能用迭代写法,赛后还能被卡半天!

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
int ans=1;
int m=1e9+7;
int fastPow(int a,int b,int m){
   int ans=1;
    while(b>0){
        if(b&1) ans=ans*a%m;
        a=a*a%m;
        b>>=1;
    }
    return ans;
}
//a*x1+b*y1=b*x2+a%b*y2
//=b*x2+(a-a/b*b)*y2
//=a*y2+b*(x2-a/b*y2)
//综上可知:x1=y2;  y1=x2-a/b*y2
int exgcd(int a,int b,int& x,int& y){
    if(b==0){
        x=1;
        y=0;
        return a;
    }
    int g=exgcd(b,a%b,x,y);
    int t=x;
    x=y;
    y=t-a/b*y;
    return g;
}
int p[100010];
int e[100010];
signed  main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>p[i]>>e[i];
    }
    int b;
    cin>>b;
    //将原式子进行拆解
    //(a-a%b)/b%m = (a%m-a%b)%m*1/b%m
    int ans1=1;  //记录a%m
    int ans2=1;  //记录a%b
    for(int i=1;i<=n;i++){
        ans1=ans1*fastPow(p[i],e[i],m)%m;
        ans2=ans2*fastPow(p[i],e[i],b)%b;
    }
    /*
    //因为已知1e9+7是质数,且b<1e9+7,所以b,m一点互质
    //满足费马小定理
    int bn=fastPow(b,m-2,m);
    cout<<(ans1-ans2+m)%m*bn%m<<endl;
    */
    int x,y;
    //b*x+m*y=1
    int g=exgcd(b,m,x,y);
    if(g!=1) return 0; //该情况不存在
    //x转化为正的乘法逆元
    x=(x%m+m)%m;
    //(ans1-ans2)也有可能是负的,所以要进行正数转换
    cout<<(ans1-ans2+m)%m*x%m<<endl;
}

费马小定理求逆元同样可以AC哈,关键在于快速幂需要用递推的写法,不能用递归。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
int ans=1;
int m=1e9+7;
int fastPow(int a,int b,int m){
   int ans=1;
    while(b>0){
        if(b&1) ans=ans*a%m;
        a=a*a%m;
        b>>=1;
    }
    return ans;
}
//a*x1+b*y1=b*x2+a%b*y2
//=b*x2+(a-a/b*b)*y2
//=a*y2+b*(x2-a/b*y2)
//综上可知:x1=y2;  y1=x2-a/b*y2
int exgcd(int a,int b,int& x,int& y){
    if(b==0){
        x=1;
        y=0;
        return a;
    }
    int g=exgcd(b,a%b,x,y);
    int t=x;
    x=y;
    y=t-a/b*y;
    return g;
}
int p[100010];
int e[100010];
signed  main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>p[i]>>e[i];
    }
    int b;
    cin>>b;
    //将原式子进行拆解
    //(a-a%b)/b%m = (a%m-a%b)%m*1/b%m
    int ans1=1;  //记录a%m
    int ans2=1;  //记录a%b
    for(int i=1;i<=n;i++){
        ans1=ans1*fastPow(p[i],e[i],m)%m;
        ans2=ans2*fastPow(p[i],e[i],b)%b;
    }
  
    //因为已知1e9+7是质数,且b<1e9+7,所以b,m一点互质
    //满足费马小定理
    int bn=fastPow(b,m-2,m);
    cout<<(ans1-ans2+m)%m*bn%m<<endl;
      /*
    int x,y;
    //b*x+m*y=1
    int g=exgcd(b,m,x,y);
    if(g!=1) return 0; //该情况不存在
    //x转化为正的乘法逆元
    x=(x%m+m)%m;
    //(ans1-ans2)也有可能是负的,所以要进行正数转换
    cout<<(ans1-ans2+m)%m*x%m<<endl;
    */
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值