备战蓝桥杯--快速幂、矩阵快速幂、逆元

快速幂实现方法

待解决的基本问题:
给你a,b,p 求a^b % p , a,b,p<=10^9
此时a b p均在int 范围内,用int表示

如果用简单循环累乘的方法,时间复杂度为O(N),基本上就运行超时了

int ans=1;
    for(int i=0;i<b;i++)
    {
    	ans=ans*a%p;   //循环累乘,会超时
    }

快速幂就是快速求幂次,将时间复杂度降为O(logn)

递归实现思路和方法

思路:
假设求3^17 ,我们想对它进行拆分一下,看能不能缩小数据规模,
拆成3^2 * 3^ 15,(⊙o⊙)…好像没啥用,算3 ^ 15和3 ^ 2时间复杂度还是O(n)
有了,我拆成俩一样的,3 ^8 *3 ^8 *3,哈哈,这时候我就只需要算3 ^8次方啦,最多后面再算一步,乘一个3,
相当于T ( n ) =T ( n/2 ) +1 ,这不就把时间复杂度降为O(logn)了嘛

如果幂次是偶数更方便,都不需要最后单独再乘一次
上代码:

long long ksm(int a,int b,int p)
{
	if(b==0)  //递归出口 
	{
		return 1%p;
	}
	if(b%2==0)  //幂次为偶数 
	{
		long long now=ksm(a,b/2,p);
		return (now*now)%p;
	}
	else        //幂次为奇数 
	{
		long long now=ksm(a,b/2,p);
		return (now*now*a)%p;
	}
	
}

再想想,观察一下,代码思路有没有什么会出错的地方,
代码中有个now*now,两个long long类型,或者就算now是int类型,两个相乘,会怎么样,是不是很有可能就超出long long的数据范围了,那我们改一下,两处分别改为
return (now % p * now % p)%p
return (now % p * now % p * a % p ) % p
这样改,可以,先取一下余以后再相乘嘛
那万一p的类型很大,比如取值为10^9,now对p取余以后相乘还是会超过long long的数据范围,这时候就需要对这个乘法运算进行优化

此时待解决的基本问题:
给你a,b,p 求a * b % p , a,b,p<=10^18 数据范围增大很多
此时a b p均在long long 范围内,用long long表示

如果用简单循环累加的方法,时间复杂度为O(N),基本上就运行超时了

采用快速幂同样的思路,只是换一下符号,
上代码:

long long ksc(int a,int b,int p)
{
	if(b==0)  //递归出口 
	{
		return 0;
	}
	if(b%2==0)  //幂次为偶数 
	{
		long long now=ksc(a,b/2,p);
		return (now+now)%p;
	}
	else        //幂次为奇数 
	{
		long long now=ksc(a,b/2,p);
		return ((now+now)%p+a)%p;
	}
	
}

此时,当a , b , c <=10^18 时前面的快速幂可优化为:

long long ksm(int a,int b,int p)
{
	if(b==0)  //递归出口 
	{
		return 1%p;
	}
	if(b%2==0)  //幂次为偶数 
	{
		long long now=ksm(a,b/2,p);
		return ksc(now,now,p);
	}
	else        //幂次为奇数 
	{
		long long now=ksm(a,b/2,p);
		return ksc(ksc(now,now,p),a,p);
	}
	
}

非递归实现快速幂

将幂次看做二进制形式,底数不断倍数自增

//两种理解方式,第二种好理解一些,但是使用位运算可提高运算效率(判断奇偶)
typedef long long ll;
int poww(int a, int b)
{
	ll ans = 1, base = a;
	while (b != 0)
	{
		//cout << b << endl;
		if (b & 1)   //判断b二进制表示的最后一位是不是0,最后一位为1是奇数,最后一位是0是偶数
		{
			ans *= base;
			//cout << ans<<endl;
		}
		base *= base;  //base自乘得到数的每一个拆分项  base2、base4、base8、base16、base32
						//base^2^1、base^2^2、base^2^3、base^2^4、base^2^5
		b >>= 1;   //右移一位,他的作用是将1011变成101–>10–>1
	}
	return ans;
}
int poww(int a, int b,int m)    //程序对数据很大时会要求取模输出
{								//(a*b)%c=a%c*b%c;每乘上一个数就对它进行一次取模
	ll ans = 1;   //结果要用long long 类型存储
	while (b)   
	{
		if (b % 2 == 1)//如果幂为奇数先让结果乘以底
		{
			ans = (ans * a) % m;        //如果ans类型为int,强制将其转换为long long类型:ans=1LL*(ans*a)%m
		}
		a = (a * a) % m;  //幂次不为0 就一直使底数自增
		b /= 2;
	}
	return ans;
}   //快速乘,与快速幂类似,快速幂是累乘,快速乘是累加 
int qmul(ll a,ll b,ll m)
{
	ll ans=0;
	while(b)   
	{
		if(b&1)   //根据b的每一位看乘不乘当前a 
		{
		ans=(ans+a)%m;	   //ans*=a;
		}
		a=(a+a)%m;    //更新a 
		b>>=1;
	}
	return ans;
}

快速幂的应用

一、费马小和快速幂求逆元

对于简单的取模运算:(经常用于大数据计算时)

加法:(a+b)%c=(a%c+b%c)%c;
减法:(a-c)%c=((a%c-b%c)%c+c)%c;
乘法:(a * b)%c=( ( a % c ) * ( b % c ) ) % c;
除法: 没有像上面一样的公式,大数据除法,需要用数论倒数知识

a和p互质,a才有关于p的逆元(a和p没有除1之外的公因数即a或b有一个为质数)
(a / b) % p = (a * inv(a) ) % p = (a % p * inv(a) % p) % p
a^(p-2) ≡ inv(a) (mod p)
inv(a) = a^(p-2) (mod p)
要求a和p互质,并且p也要足够大,所以一般p值都取1e9+7
也就是说要求inv(a) ,只需要求a^(p-2) ,当数据很大时这时候就需要用到快速幂
上代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//求大数据a除以b并将结果对p取余
ll power(ll a,ll b,ll p)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%p;
		a=a*a%p;
		b>>=1;
	}
	return ans;
 } 
ll inv(ll a,ll p)
{
	return power(a,p-2,p);
}
/*const ll m=1e9+7;
ll power(ll a, ll n)
{
    ll ot = 1;
    while (n)
    {
        if (n & 1) ot = ot * a % m;
        a = a * a % m;
        n >>= 1;
    }
    return ot;
}
ll inv(ll x)
{
    return power(x, m - 2);
}*/
int main()
{
	const ll p=1e9+7;    //这个p值一般要取很大,差不多都是这个数 
	ll a,b;
	cin>>a>>b;
	cout<<a*inv(b,p)%p;
	return 0;
 } 

二、矩阵快速幂

具体详情可以参考这篇博客:
矩阵快速幂

加一些自己的理解:
题目直接给出基本递推式,然后自己写出矩阵递推式的很少,基本都需要自己读题,然后根据题目意思先写出基本递推式,然后再写出矩阵递推式
写矩阵递推式时先搭框架,然后填数

f ( n ) =a * f ( n-1 )+b * f ( n - 2 ) +c;
在这里插入图片描述
先写右边除系数以外的主子列
然后根据这个写出左边的矩阵,最后确定系数矩阵的大小,填充系数矩阵

在这里插入图片描述
缺什么就添上什么,系数矩阵中不能有未知数n等
f ( n ) =a *f ( n - 1 ) +b * f ( n-2 ) +n^3
在这里插入图片描述
写出最基本的递推矩阵后,不断往下推,直到首相
f(n)=f(n-1)+f(n-2)
在这里插入图片描述
在这里插入图片描述
此时就可以直接调用矩阵快速幂进行求解这个递推式了

矩阵快速幂求解的基本问题

求斐波拉契数列f(n)=f(n-1)+f(n-2)的第n项%1000000007的值
当n<=10^8时:直接循环递推
当n<=10^18时就需要用到矩阵快速幂(上文给出了解法)

题目参考:
标准的矩阵快速幂:https://www.luogu.com.cn/problem/P3390
附上完整AC代码(建议自己先过一下这道题)

#include<iostream>
using namespace std;
#define MAXSIZE 105
//在写矩阵的快速幂时就把矩阵看作是常数,看作常数的快速幂来写 
typedef long long ll;   //由于余数p的数据范围超过了int,需要用long long,所以统一程序所有的数据类型为long long 
ll C[MAXSIZE][MAXSIZE];
ll A[MAXSIZE][MAXSIZE];
ll AK[MAXSIZE][MAXSIZE];
ll p=1000000007;        //经常会取这个数作为余数,记一下 
int n;
void cheng(ll Ai[][MAXSIZE],ll Bi[][MAXSIZE])  //传入的二维数组的列必须为常数(易错易混) 
{
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		{
			C[i][j]=0;     //一定要把暂存结果数组清0 
			for(int k=0;k<n;k++)
			C[i][j]=(C[i][j]+Ai[i][k]*Bi[k][j])%p; //一边加一边取余 
		}
	}
	for(int i=0;i<n;i++)   //时刻保证每一次矩阵相乘后结果都由Ai第一个参数返回 
	{
		for(int j=0;j<n;j++)
		{
			Ai[i][j]=C[i][j];
			//cout<<Ai[i][j]<<" "; 
		} 
	//	cout<<endl;
	}
}
void ksm(ll Ai[][MAXSIZE],ll k)
{
	for(int i=0;i<n;i++) AK[i][i]=1;  //初始化单位矩阵E,相当于常数的1,对角线元素均为1 
	while(k)  
	{
		if(k&1) cheng(AK,A);
		cheng(A,A);
		k>>=1; //用二进制的方法,会快一些 
	}
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++) 
		Ai[i][j]=AK[i][j];
	}
}

int main()
{
	ll k;
	cin>>n>>k; 
	
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		cin>>A[i][j];
	}
//	cheng(A,A);
	ksm(A,k);
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
		cout<<A[i][j]<<" ";
		cout<<endl;
	}
	return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值