如果我们要计算 mod p,我们首先能想到的便是for循环:
int ans=1;
for(int i=1;i<=b;i++)
ans*=a;
return ans%p;
下面我们来看看两个例子,想一想会出现什么奇葩结果呢?
奇葩结果1: 2100 mod 1000 = 0
奇葩结果2: 5212 mod 1000 ……
那第一个奇葩结果就是 2^100 已经溢出了。
第二个结果我们能一眼看出来,就是循环次数太多了。所以说解决这个问题O(n)是不行的。
此时我们需要引进一个取模公式
(axb)%p=[(a%p)x(b%p)]%p
怎样将这个公式运用到编程里面,就需要边乘边取余,最后结果再取余
int ans=1;
for(int i=1;i<=b;i++)
ans=(ans%p * a%p)%p;
return ans;
引进这节课的新算法,那就是快速幂算法,是一个O(logn)
的算法,下面来讲一下递归实现和非递归实现的方式。
学快速幂之前我们需要先了解以下公式:
(+)= *
算法过程是将b不断地二分,先来看看两个简单的例子。
例1: 例2:
516= 58 * 58 59 = 54 * 54 *5
58 = 54 * 54 54 = 52 * 52
54 = 52 * 52 52 = 51 * 51
52 = 51 * 51 51 = 50 * 5
51 = 50 * 5
首先,我们应该知道任何数的1次方是等于它本身的,或者任何数的0次方是等于1的,所以这两个条件就可以作为递归出口的条件,下面我们来看一下代码。
ll fast_power(ll a,ll b,ll p){
if(b==0) return 1;
a%=p;
//a^(b/2)
ll c=fast_power(a,b>>1,p);
if(b&1)
return c*c%p*a%p;
return c*c%p;
}
非递归算法我们就要用到,幂用二进制表示,还是来看看一个简单例子
213 = 2(1101)2 = 28 * 24 * 21
思路:我们可以设置一个连乘器;将幂作为循环条件,连乘器每次乘(当前指数的二进制最低位为1时)的底数,底数一直做平方运算,n一直在除以2。先来看看下边的例子
213= 46 * 2
46= 163
163 = 2561 * 16
此时指数已经变成零,结束循环。所以最终结果是213 =256*16 *2。 下面来看一下代码:
ll fast_power(ll a,ll b,ll p){
ll prod=1;
while(b>0){
//当最低二进制位数为1时
if(b&1) prod=prod*a%p;
a=a*a%p;
b>>=1;
}
return prod;
}
1.hdu–1420【基础】
题解
2.poj–1995【基础】
题解
3.hdu–4704【中等】
题解
题在更新中
何为矩阵快速幂,其实是我们前面用到快速幂,将底数a换为矩阵进行运算即可。
首先我们来回顾一下矩阵乘法的知识。
矩阵乘法
根据线性代数的知识我们也容易知道,两个矩阵相乘,前提是A的行数与B的列数相等,结果是其在新矩阵的行所对应的A的行与在新矩阵的列所对应的B的列对应相乘相加,所以我们可以先得出矩阵乘法的代码:
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
c[i][j]=c[i][j]+a[i][k]*b[k][j]; //这儿注意记得取模运算。
在了解了什么是矩阵以及矩阵的乘法之后我们来看下面这个熟悉的问题: Fibonacci.
不过此时的n已经到达1018,自然不可能再用我们之前那种O(n)的递推,那我们能怎么办呢?
#include
#include
#include
using namespace std;
typedef long long ll;
struct Mat{
ll m[10][10];
}arr;//arr为输入矩阵
ll MOD;
Mat Mult(Mat a,Mat b){//矩阵a X 矩阵b
Mat temp;
memset(temp.m,0,sizeof(temp.m));
for(int i=0;i<10;i++)
for(int j=0;j<10;j++)
for(int k=0;k<10;k++)
temp.m[i][j] = (temp.m[i][j] +a.m[i][k] * b.m[k][j] % MOD ) % MOD;
return temp;
}
Mat fast_power(Mat a,ll b,int n){//矩阵a的b次方,n为矩阵a的大小
//初始化为单位矩阵
Mat res;
memset(res.m,0,sizeof(res.m));
for(int i=0;i<n;i++) res.m[i][i]=1;
while(b>0){
if(b&1) res=Mult(res,a);
a=Mult(a,a);
b>>=1;
}
return res;
}
int main(){
ll n;
scanf("%lld%lld",&n,&MOD);
//初始化(f1,f2)
arr.m[0][0]=arr.m[0][1]=1;
//初始化构造矩阵
Mat A;
memset(A.m,0,sizeof(A.m));
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
A.m[i][j]=1;
A.m[0][0]=0;
//A^n-1
Mat temp=fast_power(A,n-1,2);
//最终的矩阵
Mat ans=Mult(arr,temp);
printf("%lld\n",ans.m[0][0]);
return 0;
}
首先我们看一个问题: ^ bc mod p, 0= 如果我们还是用快速幂来算的话,会出现一个什么结果呢, bc这个数会很大很大以至于unsigned long long都装不下,这时我们必须用其他思维才能解决这个问题,也就是费马小定理来优化该算法,引入费马小定理之前,我们来先来看看同余定理。
给定一个正整数m,如果两个整数a和b满足a-b能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对模m同余,记作a≡b(mod m)。对模m同余是整数的一个等价关系。
a≡b(mod m) <==> a%m ==b
假如p是质数,且gcd(a,p)=1,那么(−) ≡ 1 (mod p). <==> (−) mod p =1
由费马小定理引申出的优化。
= a(−1) * (−(−1))
==>≡ (−(−1)) (mod p)
我们发现这里如果n比p大很多,那么这个优化没有用
(∗(−1)) ≡ 1 (mod p)
令 x= (−1)
= (∗(−1)) * x
≡ x (mod p)
mod p = ax mod p
总结:如果题目中给定p是一个很大的质数,并且指数幂超级大,甚至不能用
unsigned long long 来装,这时我们就要想到费马小定理了。
1.poj–3070【基础】
题解
2.poj–3233 【基础】
题解
3.hdu–1588【中等】
题解
4.hdu–2604【中等】
题解
题在更新中