快速幂与矩阵快速幂


一、快速幂

1.原理

快速幂用于快速计算 a^b ,时间复杂度 O(log b)

是通过将指数划分为二进制,再逐一进行读取,过程中更新底数,减少总计算次数。

一般在快速幂计算中都会进行取模操作,以免溢出。

2.取模写法

#include<iostream>
using namespace std;
const int mod=1000;
typedef long long ll;
ll Pow(ll x,ll y){
	ll ans=1;
	while(y){
		if(y&1){
			ans=(ans*x)%mod;
		}
		y>>=1;
		x=(x*x)%mod;//更新底数
	}
	return ans;
}
int main(){
	ll n,m;
	cin>>n>>m;
	cout<<Pow(n,m);
}


二、矩阵快速幂

1.原理

与快速幂操作完全相同,区别在于将其中的数乘替换为矩阵乘法,所以我们需要补充一下矩阵乘法

2.代码

#include<iostream>
using namespace std; 
const int N=31;
typedef unsigned long long ull;
ull a[N][N],res[N][N],tmp[N][N];
ull n,k,m;
void  mul(ull a[][N],ull b[][N]){
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				ull ans=0;
				for(int t=0;t<n;t++){
					ans+=a[i][t]*b[t][j];
					ans%=m;
				}
				tmp[i][j]=ans%m;
			}
		}
		memcpy(a,tmp,sizeof(a));
}
void POW(){
	while(k){
		if(k%2)mul(res,a);
		mul(a,a);
		k/=2;
	}
}
int main(){
	cin>>n>>k>>m;//求n阶矩阵的k次幂,元素对m取模 
	for(int i=0;i<n;i++){
		res[i][i]=1;
		for(int j=0;j<n;j++){
			cin>>a[i][j];
		}
	}
	POW();
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			cout<<res[i][j];
			if(j<n-1)cout<<" ";
			else cout<<endl;
		}
	}
}

3.应用——斐波那契数列

斐波那契数列

此处的特点为n的范围比较大,如果我们仍然使用之前模拟的方法,哪怕我们使用循环来写,也会超时。
我们已知:
Fib(n) = Fib(n-1) + Fib(n-2) (n>1)
那么存在关系如下:
在这里插入图片描述
这是一个可以递归的式子,我们继续递归下去,可以得到:
在这里插入图片描述

也就是将最初的矩阵与一个可以在O(log n)即可求解的矩阵相乘,那么我们的时间复杂就缩减为了O(log n)

#include<bits/stdc++.h>
using namespace std;
const int maxn=2;//阶数 
const int mod=10000;
typedef long long ll; 
struct ma
{
    int a[maxn][maxn];
};
ma mul(ma a, ma b)
{
    ma ans;
    for(int i=0;i<maxn;++i)
	{
        for(int j=0;j<maxn;++j)
		{
            ans.a[i][j] = 0;
            for(int k=0;k<maxn;++k)
			{
                ans.a[i][j]+=a.a[i][k]*b.a[k][j];
                ans.a[i][j]%=mod; 
            }
        }
    } 
    return ans;
}
ma qpow(ma a,int n)
{
    ma ans;
    memset(ans.a,0,sizeof(ans.a));
   for(int i=0;i<2;i++){
   	ans.a[i][i]=1;
   }
    while(n)
	{
        if(n&1) ans=mul(ans,a);
        a=mul(a,a);
        n/=2;
    } 
    return ans;
}
int main()
{	ll n;
	while(cin>>n&&(~n)){
    ma a;
    a.a[0][0]=1;
    a.a[0][1]=1;
    a.a[1][0]=1;
    a.a[1][1]=0;
    if(n==0)cout<<0<<endl;
    else if(n==1||n==2)cout<<1<<endl;
    else{
    ma ans=qpow(a,n-2); 
    cout<<(ans.a[0][1]+ans.a[0][0])%mod<<endl;
	}
	}
    return 0;
}

4.应用场景

在这里插入图片描述


三、矩阵快速幂的构造

1.找出Cn中的第一项与哪些项有关系,将其系数作为矩阵第一行
2.确定有哪些项后,递推得知在Cn中有哪些项,将这些项分别用Cn-1中的项来表示。
在这里插入图片描述

1.形如f(n)=f(n-1)+f(n-2)

1.f(n)与f(n-1)、f(n-2)有关,那么Cn中对应有f(n)、f(n-1)
2.f(n)已经被表示,那么接下来就是用f(n-1)、f(n-2)来表示f(n)。
在这里插入图片描述

2.形如f(n)=f(n-1)+f(n-3)

补全
1.由f(n)得知至少Cn中有f(n-2)、f(n-3),但f(n-2)无法仅用f(n-1)、f(n-3)、f(n-4)来表示,所以我们将
f(n)化为3* f(n-1)+0* f(n-2)+5* f(n-3)+9* f(n-4)
2.再依次把Cn中其余的项用Cn-1中的项来表示。
在这里插入图片描述

3.形如f(n)=f(n-1)+f(n-3)+c

常数视为普通项来处理即可,Cn-1中的常数投影到Cn中仍然为那个常数。
在这里插入图片描述

4.形如f(n)=f(n-1)+f(n-3)+n^3

我们需要将Cn中的n^3以Cn-1中的(n-1)来表示,因为需要保持一致。
我们将Cn-1中的n^3以(n-1)表示后,再映射到Cn中,会得到相应的几项,再把这几项以Cn-1中的项来表示。
在这里插入图片描述

5.求前缀和,额外构造s(n)

区间求和,特别是对于这么大的范围,我们会使用前缀和,但我们求前缀和的方法并不是遍历,我们时间空间可能都会爆掉,所以我们 使用矩阵快速幂求前缀和。 两次就可以得到结果。

递推公式:S(n)=S(n-1)+f(n)=S(n-1)+f(n-1)+f(n-2)+f(n-3)
将S(n)以Cn-1中的项来表示,同上,将项映射到Cn中。
在这里插入图片描述

6.求各项平方之和,构造s(n)

由S(n)得出Cn-1中的项,可能需要思考一下Cn中的f(n)*f(n-1)如何以Cn-1中的项表示,也就是对f(n)进行替换,再展开就行了。
在这里插入图片描述

7.构造二维状态

递推分析:

对于第n秒 ,第 L 个字符来说,它的状态于第n-1秒时位置L的状态和第n-1秒时L左边的状态有关。
若f(n-1,L-1)为0,f(n-1,L)为0,则f(n,L)为0;
若f(n-1,L-1)为0,f(n-1,L)为1,则f(n,L)为1;
若f(n-1,L-1)为1,f(n-1,L)为0,则f(n,L)为1;
若f(n-1,L-1)为1,f(n-1,L)为1,则f(n,L)为0;
由此不难看出f(n,L)与f(n-1,L-1)、f(n-1,L)f(n,L)的关系,
f(n,L)=(f(n-1,L-1)+f(n-1,L))%2,而取模不会影响结果,所以可以在后面取模,
那么**f(n,L)=f(n-1,L-1)+f(n-1,L)**就是我们的递推公式。

构造矩阵(根据n的大小判断一下要不要使用矩阵快速幂):
与之前的区别在于这是一个二维状态,大胆一点!!!
在这里插入图片描述
在这里插入图片描述


四、矩阵幂求和

矩阵幂求和

1.分析

在这里插入图片描述
对于一般的快速幂,我们一般无法直接使用公式再对结果取模,因为可能会爆掉,但我们也无法直接使用取模运算里面的规则,公式中存在除法,涉及逆元操作。
所以我们的另一个常用思路就是使用二分递归。
对于矩阵来说,只需要把提出来的1以单位矩阵E来表示即可

2.代码

#include<iostream>
#include<cstring>
using namespace std;
const int N = 31;
int n, k;
int mod;
int E[N][N];
int w[N][N];

void add(int A[][N], int B[][N]){  // 计算矩阵 A + B 的结果,并存储在 A 中。
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            A[i][j] = (A[i][j] + B[i][j]) % mod;
}

void multi(int A[][N], int B[][N]){// 计算矩阵 A * B 的结果,并存储在 A 中。

    static int ans[N][N];
    memset(ans,0,sizeof(ans));
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            for (int k = 1; k <= n; k ++ )
                ans[i][j] = (ans[i][j] + A[i][k] * B[k][j]) % mod;
    memcpy(A, ans, sizeof ans);
}

void qmi(int A[][N], int k){       // 计算 A 的 k 次方的结果,并存储在 A 中。
    static int ans[N][N];
    memset(ans,0,sizeof(ans));
    for(int i=1;i<=n;i++){
  	ans[i][i]=1;
    }
    while (k){
        if (k & 1) multi(ans, A);
        multi(A, A);
        k >>= 1;
    }
    memcpy(A, ans, sizeof ans);
}

void S(int k){                // 计算 S[k],并存储在矩阵 w 中。 
    if (k == 1) return ;
    int tmp[N][N];
    memcpy(tmp, w, sizeof w); // 在计算前要先将 w 复制到 tmp 中
    if (k & 1){               // 如果 k 是奇数
        S(k - 1);             // 先计算 S(k - 1),并存储在矩阵 w 中。
        multi(w, tmp);        // 计算 w * 原w 的结果,并存储在矩阵 w 中。
        add(w, tmp);          // 计算 w + 原w 的结果,并存储在矩阵 w 中。
    }
    else{
        S(k >> 1);            // 计算 S(k / 2),并存储在矩阵 w 中。
        qmi(tmp, k >> 1);     // 计算 原w 的 k / 2 次方,并存储在矩阵 tmp 中。
        add(tmp, E);          // 计算求完快速幂后的 原w + E 的结果,并存储在 tmp 中。
        multi(w, tmp);        // 计算 w * (E + 原w ^ (k / 2)) 的结果,并存储在 w 中。
    }
}
int main(){
    cin>>n>>k>>mod;
	 // 初始化矩阵 E
    for(int i=1;i<=n;i++){
  	 E[i][i]=1;
   	}
	for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ ){
            cin>>w[i][j];
            w[i][j] %= mod;
        }
    S(k);
    for (int i = 1; i <= n; i ++ ){
        for (int j = 1; j <= n; j ++ )
            cout<<w[i][j]<<" ";
        cout<<endl;
    }
    return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值