邻接矩阵是一种用矩阵形式表示图的方法
那么如果用图的邻接矩阵作矩阵乘法
会有什么神奇的性质呢
我们假设一个N个结点的无向图
我们用
G
[
u
]
[
v
]
=
G
[
v
]
[
u
]
=
1
G[u][v]=G[v][u]=1
G[u][v]=G[v][u]=1表示
u
u
u到
v
v
v有连边,否则
G
[
u
]
[
v
]
=
G
[
v
]
[
u
]
=
0
G[u][v]=G[v][u]=0
G[u][v]=G[v][u]=0
如果用这个邻接矩阵自乘会得到什么呢
模拟矩乘的运算有
G
2
[
u
]
[
v
]
=
∑
i
=
1
n
G
[
u
]
[
i
]
∗
G
[
i
]
[
v
]
G^2[u][v]=\sum_{i=1}^nG[u][i]*G[i][v]
G2[u][v]=∑i=1nG[u][i]∗G[i][v]
也就是说
G
2
[
u
]
[
v
]
G^2[u][v]
G2[u][v]是表示图上
u
u
u到
v
v
v恰好经过两条边的路径的条数的矩阵
具体点解释
我们可以把原始邻接矩阵
G
[
u
]
[
v
]
G[u][v]
G[u][v]看作为
表示图上
u
u
u到
v
v
v恰好经过一条边的路径条数的矩阵
那么
G
2
[
u
]
[
v
]
=
∑
i
=
1
n
G
[
u
]
[
i
]
∗
G
[
i
]
[
v
]
G^2[u][v]=\sum_{i=1}^nG[u][i]*G[i][v]
G2[u][v]=∑i=1nG[u][i]∗G[i][v]显然就是运用了乘法原理与加法原理
再考虑
G
3
[
u
]
[
v
]
G^3[u][v]
G3[u][v]呢
由
G
3
G^3
G3的计算过程
G
3
[
u
]
[
v
]
=
∑
i
=
1
n
∑
j
=
1
n
G
[
u
]
[
i
]
∗
G
[
i
]
[
j
]
∗
G
[
j
]
[
v
]
G^3[u][v]=\sum_{i=1}^n\sum_{j=1}^nG[u][i]*G[i][j]*G[j][v]
G3[u][v]=∑i=1n∑j=1nG[u][i]∗G[i][j]∗G[j][v]
同理可知其表示为图上
u
u
u到
v
v
v恰好经过三条边的路径条数的矩阵
或者我们也可以将其看作
G
3
=
G
2
∗
G
G^3=G^2*G
G3=G2∗G理解,其本质是相同的
由上述不难发现该性质对于一般的正整数k都是成立的
即
G
k
[
u
]
[
v
]
G^k[u][v]
Gk[u][v]是表示图上
u
u
u到
v
v
v恰好经过K条边的路径条数的矩阵
也就是说
如果需要在某个图上求
u
u
u到
v
v
v恰好经过
K
K
K条边的路径的条数
我们完全可以使用矩阵快速幂来优化这个计算过程
当然,这个性质对于有向图以及有重边的图同样适用
对于有重边的图,把初始矩阵
G
[
u
]
[
v
]
G[u][v]
G[u][v]改成记录
u
,
v
u,v
u,v之前边的条数即可
有关该性质的应用
BZOJ1898 || 洛谷P2579 [ZJOI2005]沼泽鳄鱼【矩阵DP】
知道了上述性质之后我们再提出一个拓展性的问题
给定一个N个结点的无向图,求
u
u
u到
v
v
v恰好经过
K
K
K条边的最短路
路径条数改成了求最短路,矩阵乘法是否还适用呢
答案是肯定的,只是我们需要对矩阵乘法的定义稍加修改
G
[
u
]
[
v
]
G[u][v]
G[u][v]表示
u
u
u到
v
v
v恰好经过一条边的最短路
那么可以有
G
2
[
u
]
[
v
]
=
M
i
n
i
=
1
n
(
G
[
u
]
[
i
]
+
G
[
i
]
[
v
]
)
G^2[u][v]=Min_{i=1}^n(G[u][i]+G[i][v])
G2[u][v]=Mini=1n(G[u][i]+G[i][v])
对于一般的K,
G
k
=
G
k
−
1
∗
G
G^k=G^{k-1}*G
Gk=Gk−1∗G显然也是成立的
重新定义之后依然满足结合律,所以快速幂也依然适用
POJ - 3613 Cow Relays
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=500;
int n,m,s,t,tot;
int hh[1000010];
struct node{int a[maxn][maxn];}rem;
struct matrix
{
int mat[maxn][maxn],row,col;
matrix(int r=0,int c=0){
row=r; col=c;
for(int i=1;i<=row;++i)
for(int j=1;j<=col;++j)
mat[i][j]=0;
}
};
matrix operator *(matrix a,matrix b){
matrix c=matrix(a.row,b.col);
memset(c.mat,63,sizeof(c.mat));
for(int i=1;i<=a.row;++i)
for(int j=1;j<=b.col;++j)
for(int k=1;k<=a.col;++k)
c.mat[i][j]=min(c.mat[i][j],a.mat[i][k]+b.mat[k][j]);
return c;
}
matrix qpow(matrix a,int k)
{
matrix res=a; k--;
while(k){
if(k&1) res=res*a;
a=a*a; k>>=1;
}
return res;
}
int main()
{
n=read();m=read();s=read();t=read();
matrix rem;
memset(rem.mat,63,sizeof(rem.mat));
for(int i=1;i<=m;++i)
{
int dis=read(),u=read(),v=read();
if(!hh[u]) hh[u]=++tot;
if(!hh[v]) hh[v]=++tot;
if(rem.mat[hh[u]][hh[v]]>dis)
rem.mat[hh[u]][hh[v]]=rem.mat[hh[v]][hh[u]]=dis;
}
rem.row=rem.col=tot;
matrix tt=qpow(rem,n);
printf("%d",tt.mat[hh[s]][hh[t]]);
return 0;
}
- 参考文献——俞华程《矩阵乘法在信息学中的应用》