在讲矩阵快速幂之前,先来回顾快速幂:
快速幂
用于解决当n很大时的情况。通常与取模同时应用。
=
。难道我们真的要这么求吗??????
不可能的!!
经过快速幂算法,我们会成功地将时间复杂度从降低为
如何实现呢?举个栗子:=
=
,即
,如果n为奇数,
,那么换成代码就是:
int fastpow(int m,int n,int k)
{
int l=1;
while(k!=0)
{
if(k%2)
l*=m;
m*=m;
k/=2;
}
return l;
}
下面是矩阵快速幂!!!
矩阵快速幂
首先复习一下,矩阵乘法(洛谷是很好的!!)
(来源:洛谷P3390 【模板】矩阵快速幂 题目背景)
矩阵乘法的代码:
for(ll i=1;i<=n;i++)
for(ll j=1;j<=n;j++)
for(ll k=1;k<=n;k++)
c[i][j]+=a[i][k]*b[k][j];
好了,有了矩阵乘法法则,我们就来看看对于有递推公式,但是数据范围很大的情况下如何解决问题::
有如原始公式:
可以把它转化成矩阵相乘::
我们这就完成了矩阵快速幂的构造(关于构造方法在下文)
所以我们现在就有了一个矩阵
一直递归下去,得到的最终结果是:
因此,想要得到,我们只需要求出
就行
这就用到了矩阵快速幂
很简单,只需把快速幂变量换成定义矩阵的struct就行
juzhen fastpow(juzhen m,ll n,ll k)
{
k--;
juzhen l=m;
while(k!=0)
{
if(k%2)
l=cheng(l,m);//矩阵乘法
m=cheng(m,m);//矩阵乘法
k/=2;
}
return l;
}
接下来说说构造:
一、多项式
辅助矩阵一定是方阵,并且它的阶数等于递归方程的递归深度
二、有常数的多项式
由常数,当做多一阶处理
三、有变量的多项式
有变量,使用二次项定理将其分解,在当做多个阶来处理
因为
于是有了:
四、多项式的区间和
求 的值
(前缀和!!!)——想到啥了?用求和之差!!
设,
则
故
所以
五、含乘法项
,求
推导:
,
所以
六、二维递归方程
对于二维递归方程,
构造::
下面是一些题目::
P3390 【模板】矩阵快速幂
板子题,主要就是快速幂和矩阵乘法两个函数,没有太大思维量
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=105;
const int md=1e9+7;
ll n,k;
struct juzhen
{
ll a[N][N];
}p;
juzhen cheng(juzhen s,juzhen b)
{
juzhen c;
memset(c.a,0,sizeof(c.a));
for(ll i=1;i<=n;i++)
for(ll j=1;j<=n;j++)
for(ll k=1;k<=n;k++)
c.a[i][j]=(c.a[i][j]%md+(s.a[i][k]*b.a[k][j])%md)%md;
return c;
}
juzhen fastpow(juzhen m,ll n,ll k)
{
k--;
juzhen l=m;
while(k!=0)
{
if(k%2)
l=cheng(l,m);
m=cheng(m,m);
k/=2;
}
return l;
}
int main()
{
cin>>n>>k;
for(ll i=1;i<=n;i++)
for(ll j=1;j<=n;j++)
{
cin>>p.a[i][j];
if(k==0)
{
p.a[i][j]=0;
p.a[i][i]=1;
}
}
juzhen ans=fastpow(p,n,k);
for(ll i=1;i<=n;i++)
{
for(ll j=1;j<=n;j++)
cout<<ans.a[i][j]%md<<" ";
cout<<endl;
}
}
P1939 矩阵加速(数列)
根据题目给定的递推公式,先构造一个矩阵,然后进行矩阵幂运算,最后求an也就是最后矩阵第一行的前两个数相加即可(一开始以为是第一个和第三个,wa了好几次。。)
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int md=1e9+7;
ll t;
ll n;
struct jz
{
ll a[4][4];
}h;
jz quick_pow(jz p,jz c)
{
jz b;
memset(b.a,0,sizeof(b.a));
for(ll i=1;i<=3;i++)
for(ll j=1;j<=3;j++)
for(ll k=1;k<=3;k++)
b.a[i][j]=(b.a[i][j]%md+(p.a[i][k]*c.a[k][j])%md)%md;
return b;
}
ll cf(jz q,ll n)
{
if(n==1||n==2||n==3)
return 1;
ll k=n-3;
jz o=h;
while(k)
{
if(k%2)
q=quick_pow(q,o);
o=quick_pow(o,o);
k/=2;
}
return q.a[1][1]+q.a[1][2];
}
int main()
{
cin>>t;
memset(h.a,0,sizeof(h.a));
h.a[1][1]=1;
h.a[1][3]=1;
h.a[2][1]=1;
h.a[3][2]=1;
for(int i=1;i<=t;i++)
{
cin>>n;
cout<<cf(h,n)%md<<endl;
}
}
P1397 [NOI2013] 矩阵游戏
其实这题不是特别难,放在蓝题里面属实有点水分,不过数据范围,额,也的确恶心的可以
题目给了三个递推公式,可是根据这个构造不出矩阵来,于是需要进行推导:
所以一开始可以得到
然后一直往下递推,就有:
可以设
那么
再根据第三个递推式得:
代入上面第二行的式子:
所以现在就可以来构造矩阵了:
再来设
所以
又因为
最后代入得到
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int md=1e9+7;
const int mdd=md-1;
ll n,m,a,b,c,d;
struct jz
{
ll matrix1[3][3];
ll matrix2[3][3];
ll matrix3[3][3];
}p;
inline void readBigInt(ll &ans1, ll &ans2)
{
ans1=0,ans2=0;
char ch=getchar();
while(!isdigit(ch))
ch=getchar();
while(isdigit(ch))
{
ans1=(ans1<<3)+(ans1<<1)+(ch^48); // 返回两种取模结果
ans1%=md;
ans2=(ans2<<3)+(ans2<<1)+(ch^48);
ans2%=mdd;
ch=getchar();
}
}
jz quick_pow1(jz p,jz c)
{
jz x;
memset(x.matrix1,0,sizeof(x.matrix1));
for(ll i=1;i<=2;i++)
for(ll j=1;j<=2;j++)
for(ll k=1;k<=2;k++)
x.matrix1[i][j]=(x.matrix1[i][j]%md+(p.matrix1[i][k]*c.matrix1[k][j])%md)%md;
return x;
}
jz quick_pow3(jz p,jz c)
{
jz x;
memset(x.matrix3,0,sizeof(x.matrix3));
for(ll i=1;i<=2;i++)
for(ll j=1;j<=2;j++)
for(ll k=1;k<=2;k++)
x.matrix3[i][j]=(x.matrix3[i][j]%md+(p.matrix3[i][k]*c.matrix3[k][j])%md)%md;
return x;
}
jz cf1(jz q,ll k)
{
jz h=q;
while(k)
{
if(k%2)
h=quick_pow1(h,q);
q=quick_pow1(q,q);
k/=2;
}
return h;
}
jz cf3(jz q,ll k)
{
jz h=q;
while(k)
{
if(k%2)
h=quick_pow3(h,q);
q=quick_pow3(q,q);
k/=2;
}
return h;
}
int main()
{
ll n1,n2,m1,m2;
readBigInt(n1,n2),readBigInt(m1,m2);
cin>>a>>b>>c>>d;
a==1? (n=n1,m=m1):(n=n2,m=m2); // 特判
p.matrix1[1][1]=a%md;
p.matrix1[1][2]=1;
p.matrix1[2][2]=1;
jz ans1=cf1(p,m-2);
ll x1=ans1.matrix1[1][1]%md;
ll y1=ans1.matrix1[1][2]%md;
p.matrix3[1][1]=(x1*c)%md;
p.matrix3[1][2]=1;
p.matrix3[2][2]=1;
jz ans3=cf3(p,n-2);
ll x2=ans3.matrix3[1][1]%md;
ll y2=ans3.matrix3[1][2]%md;
ll cs=((x1*d)%md+(y1*b)%md)%md;
cout<<((((x1+(y1*b)%md)%md)*x2)%md+(y2*cs)%md)%md<<endl;
}
哦对,一开始只过了一半数据点,原因是忽略了数据范围。。。
。。。。。。。。。。。。。。这太抽象了!!!!!!!
然后题解里神犇们用了字符串读入 并取模!!!总之 t q l ,然后CV到了自己代码里嘿嘿~~
最后hack了两个数据点:
34578734657863487 38465873465876348 1 23734637 3892734 3849
467549493
1145141919810 1000000007 2 1 1 2
283823589
不知道为啥,请求大佬指正!!
~~完结撒花~~~