待解决的基本问题:
给你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<
}
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
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
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<
}
// cout<
}
}
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;
}