笔记·简单矩阵
笔记·简单矩阵
方阵#
行数等于列数的矩阵称为方阵。方阵是一种特殊的矩阵。对于「
阶矩阵」的习惯表述,实际上讲的是 阶方阵。阶数相同的方阵为同型矩阵。
主对角线#
对于矩阵
单位矩阵 #
一般用
一个矩阵乘上一个大小合适的单位矩阵等于它本身。
矩阵乘法#
两个矩阵
设矩阵
代码#
node cheng(node a,node b){
node ans;
for(int i=0;i<=siz;i++){
for(int j=0;j<=siz;j++){
for(int k=0;k<=siz;k++){
ans.a[i][j]+=a.a[i][k]*b.a[k][j];
ans.a[i][j]%=mod;
}
}
}
return ans;
}
矩阵快速幂#
矩阵乘法是满足结合律的。
类比普通快速幂即可。
node ksm(node a,int b){
node ans;
for(int i=0;i<=siz;i++)ans.a[i][i]=1;
for(;b;b>>=1){
if(b&1)ans=cheng(ans,a);
a=cheng(a,a);
}
return ans;
}
矩阵加速递推#
斐波那契数列#
做法#
递推式子我们都知道:
容易在线性时间内求出这个玩意。
然后我们来求求
容易发现,线性太慢了。然后考虑矩阵加速递推。
首先我们把他转换成一项推出另一项的样子。
那不如吧斐波那契的两项放一块,成
然后不妨设
然后我们考虑求一个矩阵
于是:
然后用矩阵快速幂直接冲就行了。。。
代码#
signed main(){//那就……只放主函数吧!
n=read();
if(n<=2){
puts("1");
return 0;
}
ans.a[1][1]=ans.a[1][2]=1;//初始化斐波那契的前两项
base.a[1][1]=base.a[1][2]=base.a[2][1]=1;//初始化base数组
base=ksm(base,n-2);//因为已经求出了前两项,所以是n-2次方
ans=cheng(ans,base);
cout<<ans.a[1][1];
return 0;
}
广义斐波那契数列#
题意#
广义的斐波那契数列是指形如
的数列。
今给定数列的两系数和 ,以及数列的最前两项 和 ,另给出两个整数 和 ,试求数列的第 项 。
做法#
和上面那个普通斐波那契数列没啥区别,改一下值就行了。
然后矩阵就是:
直接跑即可。
代码#
signed main(){//只放主函数吧
p=read(),q=read(),a1=read(),a2=read(),n=read(),mod=read();
if(n<=2){
if(n==1)cout<<a1;
else cout<<a2;
return 0;
}
ans.a[1][1]=a2;//恶臭初始化
ans.a[1][2]=a1;
base.a[1][2]=1;
base.a[1][1]=p;
base.a[2][1]=q;
base=ksm(base,n-2);
ans=cheng(ans,base);
cout<<ans.a[1][1];
return 0;
}
【模板】矩阵加速(数列)#
题意#
已知一个数列
,它满足: 求
数列的第 项对 取余的值。
做法#
类比上面,找系数,QwQ。
然后矩阵就是:
直接跑即可。
这不是三倍经验吗
代码#
void real_main(){//啊哈哈多组数据
base.clear();
ans.clear();
cin>>n;
if(n<=3){
cout<<1<<'\n';
return;
}
base.a[1][1]=base.a[1][2]=base.a[3][1]=base.a[2][3]=1;//初始化
ans.a[1][1]=ans.a[1][2]=ans.a[1][3]=1;
base=ksm(base,n-3);
ans=cheng(ans,base);
cout<<ans.a[1][1]<<'\n';
}
signed main(){
int T=read();
while(T--)real_main();
}
Darth Vader and Tree#
题意#
有一个无限大的有根树,树上的每一个节点都有
个子节点( ),任意一个节点它的第 个子节点之间的距离为 ( )。 给出一个数
( ),求有多少个节点到根节点的距离小于等于 ,输出答案对 取模后的结果。
做法#
首先,当
其次,当
然后,然后就不会了/kk
然后向机房大佬询问
首先,从
不妨设
然后我们改进了这个
然后,我们就会了
发现这个时候要求出
我们要求
那么不妨将
那么我们每个矩阵就是:
考虑转移矩阵
但是要求
所以:
然后跑。
代码#
void init(){
for(int i=1;i<=n;i++)++t[d[i]];
sum[0]=a[0]=1;
for(int i=1;i<=siz;i++)for(int j=1;j<=i;j++)a[i]+=(a[i-j]*t[j])%mod,a[i]%=mod;
for(int i=1;i<=siz;i++)sum[i]=(sum[i-1]+a[i])%mod;
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++)d[i]=read();
init();
if(m<=siz)return cout<<sum[m],0;
base.a[0][0]=1;
for(int i=1;i<=siz;i++)base.a[i][0]=base.a[i][1]=t[i];
for(int i=2;i<=siz;i++)base.a[i-1][i]=1;
ans.a[0][0]=sum[siz];
for(int i=1;i<=siz;i++)ans.a[0][siz-i+1]=a[i];
base=ksm(base,m-100);
ans=cheng(ans,base);
cout<<ans.a[0][0];
return 0;
}
[TJOI2017]可乐和它的加强版#
题意#
加里敦星球的人们特别喜欢喝可乐。因而,他们的敌对星球研发出了一个可乐机器人,并且放在了加里敦星球的
号城市上。这个可乐机器人有三种行为: 停在原地,去下一个相邻的城市,自爆。它每一秒都会随机触发一种行为。现在给加里敦星球城市图,在第 秒时可乐机器人在 号城市,问经过了 秒,可乐机器人的行为方案数是多少?
,原版 ,加强版 。
做法#
首先,考虑暴力
发现自爆操作不好搞。
发现这个东西就相当于走到一个虚节点(比如
记
所以:
这样直接暴力
不难发现,这个东西是可以矩阵加速的。
设
根据上面的
呃呃呃也不知道艾弗森括号能不能在这里用/kk。
初始状态就是:
然后直接跑即可。
代码#
int main(){
n=read(),m=read();
for(int i=1,u,v;i<=m;i++){
u=read(),v=read();
base.a[u][v]=base.a[v][u]=1;//加边,双向边
}
t=read();
for(int i=0;i<=n;i++)base.a[i][i]=1,base.a[i][0]=1;//初始化
ans.a[0][1]=1;
base=ksm(base,t);
ans=cheng(ans,base);
for(int i=0;i<=n;i++)sum=(sum+ans.a[0][i])%mod;
cout<<sum;//得到答案
return 0;
}
[NOI Online #1 入门组] 魔法#
必须用魔法打败魔法!
题意#
做城市, 条道路,道路有费用。你要从 走到 。 神奇的是,你能使用魔法让下一次经过的边权变成它的相反数。使用魔法只是改变一次的花费,而不改变一条道路自身的费用。
。
做法#
首先,当
当然,我们想要的是
从分层图入手,设
然后考虑怎么求这个东西。
先考虑暴力
首先,当
接下来,考虑
我们发现,可以拿存在的边出来进行松驰,也就是:
这样就有个问题:我这次可不可以不用魔法捏?当然是可以的。但是上面的式子也没有算啊?为啥不加一条
首先,如果从
如果都用过了魔法,那我应该考虑绕个圈。如果绕个圈更优,我自然会再使用一次魔法。如果不是更优捏?那么,当
然后呢?矩阵加速?这玩应连个乘号都不带的,连个加号都不带的,咋矩阵加速?
呃呃呃,实际上不是矩阵加速,就是用了一下矩阵加速的思想。
容易发现,使用
也就是说,这个玩意是满足结合律的。那自然是使用快速幂(的思想)了!
问题来了:我咋一次性使用
容易发现,一次性使用
也就是:
容易发现,这个式子长得很像矩阵乘法,只是求和变成了去最小值,乘法变成了加法。
那么,只要证明我们的这个运算满足结合律就行了。
然后,感谢ChatGPT提供的理性证明/doge:
假设有三个矩阵
, , 。 首先将加法改成取最小值,乘法改为加法后的矩阵乘法表示为:
然后我们来证明
。 左边矩阵乘法:
代入定义式并展开:
右边矩阵乘法:
代入定义式并展开:
因此,左边矩阵乘法和右边矩阵乘法计算结果相同,证毕。
于是,它是对的。
代码#
struct node{
int a[maxn][maxn];
node(){memset(a,0x3f,sizeof(a));}//呃呃呃这里初始化成inf
}base;
node cheng(node a,node b){//魔改版矩阵乘法
node ans;
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
ans.a[i][j]=min(ans.a[i][j],a.a[i][k]+b.a[k][j]);
return ans;
}
node ksm(node a,int b){
node ans;
for(int i=0;i<=n;i++)ans.a[i][i]=0;//在这里的单位矩阵对角线是0,其他是inf
for(;b;b>>=1){
if(b&1)ans=cheng(ans,a);
a=cheng(a,a);
}
return ans;
}
int dis[maxn][maxn];
signed main(){
memset(dis,0x3f,sizeof(dis));
n=read(),m=read(),k=read();
for(int i=1,u,v,w;i<=m;i++){
u=read(),v=read(),w=read();
e.push_back((edge){u,v,w});
dis[u][v]=min(dis[u][v],w);
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);//量子Floyd
for(int i=1;i<=n;i++)dis[i][i]=0;
if(k==0)return cout<<dis[1][n],0;
for(edge k:e)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
base.a[i][j]=min(base.a[i][j],min(dis[i][j],dis[i][k.u]+dis[k.v][j]-k.w));//处理出k=1的情况
base=ksm(base,k);
cout<<base.a[1][n];
return 0;
}
更加强大的结论#
如果外层和内层运算有分配和结合律之类的,那么这个矩阵乘法有结合律。
可以自己手推一下。
然后#
因为它满足结合律,所以我们可以用快速幂(的思想,和代码)来进行优化。
然后,然后我们就能用它做更加离谱的加速了呀。
Flights for Regular Customers#
题意#
感谢ChatGPT提供的翻译~
在这个国家中,有
个城市,分别用从 到 的正整数编号。每个城市都有一个机场。 此外,有一家唯一的航空公司,他们有
条航线。不幸的是,如果你没有成为这家航空公司的常客,就无法使用这些航班。也就是说,只有当你之前至少乘坐了 次航班后,才能享受从城市 到城市 的第 次航班。 请注意,第
条航线恰好飞行从城市 到城市 ,不能用于从城市 到城市 的飞行。有趣的是,可能会有具有美丽天空景色的娱乐性飞行,始发地和目的地是同一个城市。 你需要从城市
前往城市 。不幸的是,你从未乘坐过飞机。你至少需要乘坐多少次航班才能到达城市 ? 请注意,同一条航线可以多次使用。
这时洛谷的简化版题面:
给定一张
个点 条边的有向图。
一开始你在号节点,你要走到 号节点去。
只有当你已经走过了至少条边时,你才能走第 条边。
问最少要走多少条边,或判断无法到达。
, 。
做法#
首先,先问一个简单的问题:经过
这个玩意直接
容易发现,
考虑矩阵加速递推:把加法换成或,乘法换成与。
容易发现,随着
怎么让他不变呢?容易发现,
然后我们就能快乐地求出
求出这个玩意有啥用捏?我们可以顺便维护一下
知道这个,就可以愉快地二分了。
复杂度捏?
算出来数的话……
首先,我们的矩阵乘法就是与啊或啊什么的,于是考虑调整循环顺序,然后使用 bitset
优化。
不妨从
// 以下文的参考代码为例
inline mat operator*(const mat& T) const {
mat res;
for (int i = 0; i < sz; ++i)
for (int j = 0; j < sz; ++j)
for (int k = 0; k < sz; ++k) {
res.a[i][j] += mul(a[i][k], T.a[k][j]);
res.a[i][j] %= MOD;
}
return res;
}
// 不如
inline mat operator*(const mat& T) const {
mat res;
int r;
for (int i = 0; i < sz; ++i)
for (int k = 0; k < sz; ++k) {
r = a[i][k];
for (int j = 0; j < sz; ++j)
res.a[i][j] += T.a[k][j] * r, res.a[i][j] %= MOD;
}
return res;
}
这是普通的矩阵乘法,重新排列循环以提高空间局部性,会在得到常数级别的提升。
然后来考虑一下位运算。也就是:
//这段是自己写的了(
//把这个:
mat operator * (const mat &nxt)const{
mat ans;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
ans.val[i][j]|=val[i][k]&nxt.val[k][j];
return ans;
}
//换成:
mat operator * (const mat &nxt)const{
mat ans;
for(int i=1;i<=n;i++)
for(int k=1;k<=n;k++)
if(val[i][k])
for(int j=1;j<=n;j++)
ans.val[i][j]|=nxt.val[k][j];
return ans;
}
//然后就可以bitset优化,把j循环去掉:
mat operator * (const mat &nxt)const{
mat ans;
for(int i=1;i<=n;i++)
for(int k=1;k<=n;k++)
if(val[i][k])
ans.val[i]|=nxt.val[k];
return ans;
}
这样复杂度就可以除上一个
然后,当我们知道走
我们从所有可到达的点开始,只经过小于等于当前
然后,然后就做完了。复杂度
代码#
struct mat{
bitset<maxn>val[maxn];
bitset<maxn> & operator [] (const int pos){//方便起见,搞个bitset加速
return val[pos];
}
mat operator * (const mat &nxt)const{//bitset加速矩阵乘法
mat ans;
for(int i=1;i<=n;i++){
for(int k=1;k<=n;k++){
if(val[i][k])
ans.val[i]|=nxt.val[k];
}
}
return ans;
}
void clear(){//清空
for(int i=0;i<maxn;i++)val[i]=0;
return;
}
}base,op;
void bfs(int lim){//lim是限制,只能走d<=lim的边
memset(dis,-1,sizeof(dis));
for(int i=1;i<=n;i++)if(base[1][i]){
q.push(i);
dis[i]=0;
}
while(!q.empty()){
int now=q.front();q.pop();
for(edge nxt:g[now]){
if(nxt.d>lim||~dis[nxt.v])continue;
dis[nxt.v]=dis[now]+1;
q.push(nxt.v);
}
}
}
int main(){
n=read(),m=read();
for(int i=1,u,v,d;i<=m;i++){
u=read(),v=read(),d=read();
e.push_back((edge){u,v,d});
g[u].push_back((edge){u,v,d});
}
sort(e.begin(),e.end());
for(edge i:e)mp.push_back(i.d);
mp.erase(unique(mp.begin(),mp.end()),mp.end());
for(edge i:e)li[lower_bound(mp.begin(),mp.end(),i.d)-mp.begin()].push_back(i);
base[1][1]=1;//一开始只能走到1号点自己
bfs(0);
if(~dis[n]){//只能走0的边
cout<<dis[n];
exit(0);
}
for(int i=1;li[i].size();i++){
op.clear();
for(int k=0;k<i;k++)for(edge j:li[k])op[j.u][j.v]=1;//加入边
op=ksm(op,mp[i]-mp[i-1]);//快速幂处理
base=base*op;
bfs(mp[i]);
if(~dis[n]){
cout<<dis[n]+mp[i];
exit(0);
}
}
puts("Impossible");
return 0;
}
Power of Matrix#
题意#
你有一个
做法#
容易发现,这个东西不好做。
先来考虑一个简单的问题:你有一个数
这个东西是有通项公式的,要用快速幂算,而且对矩阵不支持。
我们考虑使用矩阵加速递推。
我们设答案矩阵为:
那么显然转移矩阵为:
然后考虑对矩阵使用这个东西,发现可行。于是:
我们设答案矩阵为:
那么显然转移矩阵为:
然后矩阵快速幂即可。
容易发现,把这四个矩阵放在一块,成一个
代码#
struct mat{
struct matline{//矩阵的一行
int val[maxn];
matline(){memset(val,0,sizeof(val));}
inline int & operator [] (const int pos){
return val[pos];
}
inline void clear(){memset(val,0,sizeof(val));}
}val[maxn];
inline matline & operator [] (const int pos){//这样在下面用的时候会方便且美观一些
return val[pos];
}
inline mat operator * (const mat &nxt)const{//矩阵乘法
mat ans;
for(int i=1;i<=(n<<1);i++){
for(int k=1;k<=(n<<1);k++){
int tmp=val[i].val[k];
for(int j=1;j<=(n<<1);j++){
ans.val[i].val[j]=(ans.val[i].val[j]+(tmp*nxt.val[k].val[j]))%mod;
}
}
}
return ans;
}
inline void clear(){for(int i=0;i<=n;i++)val[i].clear();}
};
inline bool real_main(){//多组数据啊哈哈
a.clear();
ans.clear();
base.clear();
n=read(),m=read();
if(!n)return 0;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)a[i][j]=(read())%mod;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
base[i][j]=base[i][j+n]=ans[i][j]=ans[i][j+n]=a[i][j];//矩阵四合一
}
for(int i=1;i<=n;i++)base[i+n][i+n]=1;
base=ksm(base,m-1);
ans=ans*base;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cout<<ans[i][j+n];
if(j<n)cout<<' ';
}
cout<<'\n';
}
cout<<'\n';
return 1;
}
int main(){
for(int i=0;i<maxn;i++)I[i][i]=1;
while(real_main());
}
[USACO07NOV]Cow Relays G#
题意#
给定一张
个点 条边的无向图,边有长度。 问从
到 经过 条边的最短路长度。
。
做法#
首先,点的编号太大了,要离散化一下。于是
看有很多倍增
还是先考虑
我们设
那么转移就是:
这就很简单了。
于是,设
容易发现,矩阵乘法的加法应该改成取最小值,加法应该改成加法。
然后直接矩阵快速幂即可。
P6772 [NOI2020] 美食家#
题意#
给定一个
边有边权
某些城市会在不同的时间举办美食节,一共会举办
问在第
做法#
显然,设
转移显然:
因为
转移矩阵显然。
那么单次矩阵乘法的复杂度就是
考虑进行优化。
设
进行这次乘法会得到一个
这样似乎就把矩阵快速幂优化到
所以考虑预处理出
这样,总复杂度就是
代码#
写两种矩阵乘法。难调。
注意:美食节的时间不是按照升序给出的。
struct mat{//矩阵
int val[maxn][maxn];
mat(){//初始化为-inf
memset(val,-0x3f,sizeof(val));
}
};
struct fes{//美食节
int t,x,y;
bool operator < (const fes cmp){
return t<cmp.t;
}
}b[maxn];
int n,m,T,k;
int siz;
int a[maxn];
mat pw[maxn];
mat ans,base;
mat mul(mat a,mat b){//普通的矩阵乘法
mat ans;
for(int i=1;i<=siz;i++){
for(int j=1;j<=siz;j++){
for(int k=1;k<=siz;k++){
ans.val[i][j]=max(ans.val[i][j],a.val[i][k]+b.val[k][j]);
}
}
}
return ans;
}
mat cheng(mat a,mat b){//平方复杂度的矩阵乘法
mat ans;
for(int i=1;i<=siz;i++){
for(int j=1;j<=siz;j++){
ans.val[1][i]=max(ans.val[1][i],a.val[1][j]+b.val[j][i]);
}
}
return ans;
}
int getpos(int x,int y){
return x+y*n;
}
int lowbit(int x){
return x&(-x);
}
void ksm(int p){//改版快速幂
for(;p;p-=lowbit(p))ans=cheng(ans,pw[__builtin_ctz(p)]);
}
signed main(){
n=read(),m=read(),T=read(),k=read();
siz=n*5;
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=m;i++){//转移矩阵
int u=read(),v=read(),w=read();
base.val[getpos(u,w-1)][getpos(v,0)]=a[v];
}
for(int i=n+1;i<=siz;i++)base.val[i-n][i]=0;//转移矩阵
for(int i=1;i<=k;i++)b[i].t=read(),b[i].x=read(),b[i].y=read();
sort(b+1,b+1+k);
pw[0]=base;
for(int i=1;i<=n;i++)pw[i]=mul(pw[i-1],pw[i-1]);//处理base的2的k次幂次幂
ans.val[1][1]=a[1];//初始化ans
for(int i=1;i<=k;i++){
ksm(b[i].t-b[i-1].t);
ans.val[1][b[i].x]+=b[i].y;
}
ksm(T-b[k].t);
if(ans.val[1][1]>=0)cout<<ans.val[1][1];
else puts("-1");
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~