快速幂与矩阵快速幂【入门+基础】

快速幂

如果我们要计算𝑎𝑏 mod p,我们首先能想到的便是for循环:

int ans=1;
for(int i=1;i<=b;i++) 
	ans*=a;
return   ans%p;

下面我们来看看两个例子,想一想会出现什么奇葩结果呢?
奇葩结果1: 2100 mod 1000 = 0
奇葩结果2: 52𝑒12 mod 1000 ……
在这里插入图片描述
那第一个奇葩结果就是 2^100 已经溢出了。
第二个结果我们能一眼看出来,就是循环次数太多了。所以说解决这个问题O(n)是不行的。

1.解决溢出问题:

此时我们需要引进一个取模公式
(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;

2.降低复杂度:

引进这节课的新算法,那就是快速幂算法,是一个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<iostream>
#include<cstdio>
#include<cstring>
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=<a,b,c<=1e9; p为1e9+7并且是质数.
如果我们还是用快速幂来算的话,会出现一个什么结果呢, 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【中等】
题解
题在更新中

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星空皓月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值