一、快速幂
原理:
快速幂的原理十分简单。
ak=a2^0*a2^1*a2^2*…a2^x,其中k=20+21+22+…+2x。
这显然是正确的。因为任何一个数都可以表示成二进制。
接下去利用位运算实现即可。
代码实现
模板题链接:快速幂
代码模板如下:时间复杂度O(logk)
int qmi(int a,int k,int p) { int res=1%p; while(k) { if(k&1)res=(long long)res*a%p; a=(long long)a*a%p; k>>=1; } return res; }
值得一提的是,以上代码在过程中取模,是基于模运算的运算规则。
模运算有一些很好的性质,以下列举四条:
- (a + b) % p = (a % p + b % p) % p
- (a - b) % p = (a % p - b % p + p) % p
- (a * b) % p = (a % p * b % p) % p
- (a^b) % p = ((a % p)^b) % p
二、快速幂求逆元
乘法逆元的定义
若整数b,m互质,并且b|a,则存在一个整数x,使得a/b≡a∗x(mod m),则称x为b的模m乘法逆元,记为b−1(mod m)。
b存在乘法逆元的充要条件是b与模数m互质。当模数m为质数时,bm−2即为b的乘法逆元。
因为在模运算中,并没有除法的性质,即没有(a/b)%p≠((a%p)/(b%p))%p,而乘法逆元便可以帮助我们将其转化成乘法形式:a/b % m=a∗x % m。
由乘法逆元的定义我们可以推出如下结论:
∵ a/b mod p = a * b-1 mod p = a/b * b * b-1 mod p
∴ b * b-1 ≡ 1(mod p)
费马小定理
若p是一个质数,且整数a不是p的倍数(a与p互质),则有a p-1 ≡ 1(mod p)。
基于这个定理,我们就可以推出如下结论:
∵ a p-1 ≡ 1(mod p)
∴ a * a p-2 ≡ 1(mod p)
因此,结合乘法逆元的定义所得到的推论以及费马小定理,我们可以得到:a-1 = a p-2(mod p)。
也就是说,当模数p为质数,且整数b不是p的倍数时(b与p互质),b的逆元即为b p-2。
代码实现
模板题链接:快速幂求逆元
根据上述结论,要判断b关于模数p的乘法逆元是否存在,若存在则求出之,我们便只需要计算b p-2即可。而这个计算利用快速幂便可以很快解决。
代码如下:
#include <iostream> #include <algorithm> #include <cstdio> using namespace std; typedef long long ll; int qmi(int a,int k,int p) { int res=1%p; while(k) { if(k&1)res=(ll)res*a%p; a=(ll)a*a%p; k>>=1; } return res; } int main() { int n;scanf("%d",&n); for(int i=1;i<=n;i++) { int a,b; scanf("%d%d",&a,&b); if(a%b==0)printf("impossible\n"); else printf("%d\n",qmi(a,b-2,b)); } return 0; }
应用
有了乘法逆元,我们在计数类问题中遇到(a/b)%p时,便可以转化成((a % p) * (b-1 % p)) % p来计算了,这样便防止了爆int的情况出现,当然,转化的前提是必须保证b与p互质。当p是质数时,则可以进一步转化为((a % p) * (b p-2 % p)) % p。