文章目录
一、快速幂
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;
}