【矩阵乘法&快速乘模板】【题解】【刷题比赛】
【矩阵乘法&快速乘模板】【题解】【刷题比赛】
P1707 刷题比赛
Solution:
很明显是一道矩阵乘法加速递推,只是推转移矩阵比较复杂。
做这种题的思路是:
- 如果递推为线性递推,且暴力递推次数很多,可以考虑矩阵优化
- 先根据要计算的信息,列出一个行数为1的状态矩阵,设为。
- 再仿照写出。
- 再对的每一项分别考虑,看这一项由的哪些项乘上相应的系数转移而来,构造出转移矩阵
- 写出,则,用矩阵快速幂的模板计算即可
另外,本题在做乘法时有可能爆long long,需要用快速乘或龟速乘来避免
龟速乘
与快速幂的思路十分类似。若要计算,设
那么由乘法分配律得,
那么看代码就很容易了
#define int long long int fmul(int x,int y,int mod) { int res=0; while(y) { if(y&1) res+=x,res%=mod; y>>=1; x<<=1; x%=mod; } return res; }
快速乘
龟速乘的复杂度是带个log的,因而出现了O(1)的快速乘。
其原理跟long double的精度有关,在此不做讨论,只给出代码。
#define int long long int fmul(int x,int y,int mod) { unsigned int res= (unsigned int)x*y- (unsigned int)((long double)x/mod*y+0.5L)*mod; return (res+mod)%mod; }
最后给出本题代码
#include<bits/stdc++.h> using namespace std; #define int long long inline int read() { register int x=0,w=1; register char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-') ch=getchar(); if(ch=='-') {ch=getchar();w=-1;} while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} return x*w; } inline void write(int x) { if(x<0) {putchar('-');x=~(x-1);} if(x>9) write(x/10); putchar('0'+x%10); } const int N=11; int n,mod; int fmul(int x,int y,int mod) { unsigned int res= (unsigned int)x*y- (unsigned int)((long double)x/mod*y+0.5L)*mod; return (res+mod)%mod; } //int fmul(int x,int y,int mod) //{ // int res=0; // while(y) // { // if(y&1) res+=x,res%=mod; // y>>=1; // x<<=1; // x%=mod; // } // return res; //} struct Mat{ int a[N][N]; Mat(){ memset(a,0,sizeof a); } Mat operator*(const Mat &x)const{ Mat res; for(int i=0;i<N;++i) for(int j=0;j<N;++j) for(int k=0;k<N;++k) res.a[i][j]+=fmul(a[i][k],x.a[k][j],mod),res.a[i][j]%=mod; return res; } }f,A; Mat fpow(Mat x,int y) { Mat res=x;y--; while(y) { if(y&1) res=res*x; y>>=1; x=x*x; } return res; } signed main() { int p,q,r,t,u,v,w,x,y,z; n=read();mod=read(); cin>>p>>q>>r>>t>>u>>v>>w>>x>>y>>z; f.a[0][0]=f.a[0][1]=f.a[0][2]=f.a[0][8]=f.a[0][9]=f.a[0][10]=1; f.a[0][3]=f.a[0][4]=f.a[0][5]=3; f.a[0][6]=w;f.a[0][7]=z; A.a[0][3]=q; A.a[1][4]=v; A.a[2][5]=y; A.a[3][0]=1;A.a[3][3]=p;A.a[3][4]=A.a[3][5]=1; A.a[4][1]=A.a[4][3]=A.a[4][5]=1;A.a[4][4]=u; A.a[5][2]=A.a[5][3]=A.a[5][4]=1;A.a[5][5]=x; A.a[6][4]=1;A.a[6][6]=w; A.a[7][5]=1;A.a[7][7]=z; A.a[8][3]=r;A.a[8][8]=1; A.a[9][3]=t;A.a[9][5]=A.a[9][9]=1;A.a[9][8]=2; A.a[10][3]=A.a[10][8]=A.a[10][9]=A.a[10][10]=1;A.a[10][5]=2; f=f*fpow(A,n-1); printf("nodgd %lld\nCiocio %lld\nNicole %lld", f.a[0][0], f.a[0][1], f.a[0][2]); return 0; }
矩阵乘法的另外一种实现形式:在结构体中记录矩阵的n,m,可以减少全部当成方阵计算的常数。
Code
#include<bits/stdc++.h> using namespace std; inline int read() { register int x=0,w=1; register char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-') ch=getchar(); if(ch=='-'){ch=getchar();w=-1;} while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} return x*w; } inline void write(int x) { if(x<0){putchar('-');x=~(x-1);} if(x>9) write(x/10); putchar(x%10+'0'); } const int N=105,mod=2017; int n,m; vector<int>v[N]; struct node{ int n,m,a[N][N]; node(){memset(a,0,sizeof a);n=0,m=0;} node operator*(const node &x)const { node res; res.n=n,res.m=x.m; for(int i=0;i<res.n;++i) for(int j=0;j<res.m;++j) for(int k=0;k<m;++k) res.a[i][j]+=a[i][k]*x.a[k][j]%mod,res.a[i][j]%=mod; return res; } }F,A; int t; node fpow(node x,int y) { node res=x; y--; while(y) { if(y&1) res=res*x; y>>=1; x=x*x; } return res; } int main() { n=read();m=read(); for(int i=1;i<=m;++i) { int x=read(),y=read(); v[x].push_back(y);v[y].push_back(x); } t=read(); F.n=1,F.m=n+1; F.a[0][0]=1; A.n=A.m=n+1; for(int j=0;j<n;++j) { for(int i=0;i<v[j+1].size();++i) { int y=v[j+1][i]; A.a[y-1][j]=1; } A.a[j][j]=1; } for(int i=0;i<=n;++i) A.a[i][n]=1; int ans=0; node res=F*fpow(A,t); for(int i=0;i<=n;++i) ans+=res.a[0][i],ans%=mod; write(ans); return 0; }
注意到矩阵乘法具有结合律的本质是:数乘对数加是具有分配律的,且加法满足交换律。
因此可以推出更广义的矩阵乘法:若对具有分配律,且满足交换律,,则该矩阵乘法具有结合律,可用快速幂加速。
更简洁的表述:
若,则由定义的广义矩阵乘法满足结合律。其中为外层运算,为外层运算。
CF643F Bears and Juice
一道并不明显的矩乘优化dp
一般来说,矩乘优化递推的题目,对于题目中的,一般是开到级别的,但这题只是。还有矩乘一般是要求一个点处的值,但是这里需要对每个点的值都求出来。
如果每次暴力乘矩阵的话,复杂度跟暴力是相同的。注意到复杂度中的q是无法避免的,考虑如何避免过多的矩阵乘法。
我们对分块,设块的大小为,假设要求出点的值,那么.
也就是先暴力求出前个点的点值,后面的每个点都拆成一个矩阵乘一个向量的形式,但是矩阵乘上一个向量的复杂度仍然是,总复杂度还是,但是我们实际用到的只是向量处的一个点值,因此只需就能求出。
复杂度为,取最优。
总结一下就是当递推式可以用矩阵优化,但是需要求多个点点值时,可以分块求出。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】