引例
求 % c的结果,其中0 <= a,b,c <= 1
。
首先考虑O(n)的算法,即b次的a相乘,最后取余c。显然这种算法有两个缺点:
在数很大的时候,计算机无法表示;
- 线性时间内求解在n很大的时候非常费时。
由此我们引入的快速幂取模算法,来解决上述问题。
快速幂取模原理
首先,快速幂取模得以实现,基础是 (a * b) % c = ((a % c) * (b % c)) % c,此处不再证明这条式子。
其次,我们先将引例中的b用二进制来表示:
,其中b0,b1,b2是b用二进制表示后的低位到高位。
由以上式子,我们已经可以设计出快速幂取模算法:
- 遍历b的每一个二进制位,显然为1的时候才对结果有贡献
- 对于b的每一个二进制位,如果为1则与ans相乘,同时a不断平方(从式子中可以看出,a的增长为:
)
- 根据基础的式子,每一步都需要取余c
由于遍历的是b的二进制位数,因此只需要次就可以遍历完,时间复杂度为
。
完整代码
下面为快速幂取模的算法。
#define ll long long
ll quickMod(ll a, ll b, ll c)
{
ll ans = 1;
a = a % c; // 首先将a约束到c范围之内
while(b)
{
if(b&1) ans = (ans * a) % c;
b = b >> 1;
a = (a * a) % c;
}
return ans;
}
下面为矩阵快速幂的算法。矩阵快速幂只是将普通的乘法变成矩阵的乘法,然后初始值设为单位矩阵E,其他均为普通快速幂的框架。
#define ll long long
#define maxn 3
struct Matrix
{
ll m[maxn][maxn];
Matrix()
{
for(int i = 1; i < maxn; i++)
for(int j = 1; j < maxn; j++)
{
m[i][j] = 1;
}
}
};
Matrix Mpow(Matrix m1, Matrix m2)
{
Matrix ans;
memset(ans.m, 0, sizeof(ans.m));
for(int i = 1; i < maxn; i++)
for(int j = 1; j < maxn; j++)
for(int k = 1; k < maxn; k++)
ans.m[i][j] = (ans.m[i][j] + m1.m[i][k] * m2.m[k][j]);
return ans;
}
Matrix quickMod(Matrix a, ll b)
{
Matrix ans;
memset(ans.m, 0, sizeof(ans.m));
for(int i = 1; i < maxn; i++) ans.m[i][i] = 1;
while(b)
{
if(b&1) ans = Mpow(ans, a);
b = b >> 1;
a = Mpow(a, a);
}
return ans;
}