一、快速幂
所谓快速幂,就是一种快速求一个数的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是奇数时,。
②当b是偶数时, 。
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=p1e1×p2e2×⋯×pnen。
其中,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;
*/
}