min 与 + 运算转换成类似于矩阵乘法的推导过程
记录下由 与 运算转换成类似于矩阵乘法的推导过程,有错误请在评论区指出 qwq。
我们先简单证明一下矩阵乘法的结合律。设有矩阵 ,,,要证明 。等价于证 ,其中 表示矩阵 的第 行第 列的元素。
因此根据矩阵乘法的结合律,对于 可以写成 ,并假设 ,其中 ,,利用结合律我们就可以通过 的复杂度来计算得到
其中上面的证明过程用到的运算包括 与 以及对应的交换律、结合律和分配律。对于矩阵乘法
为了更一般化,我们用运算符 和 来代替上面的加法与乘法,得到:
其中 和 满足交换律:
结合律:
对 的分配律:
很明显矩阵乘法中的加法 和 就分别对应于 和 。同时发现把 代入 , 代入 ,也满足上面定义的一般形式,只需验证这三个性质均成立即可。
交换律:
结合律:
对 的分配律:
因此有
因此对于由 与 所构成的运算是具有结合律的,因此对于 同样可以写成 ,并且有
注意这里的 是指两个矩阵间的运算,不是一般意义的矩阵乘法。对于 和 有
而一般意义的矩阵乘法是
我们通过 Floyd求最短路 这道题目对这个性质进行运用。
这里我们不用传统 Floyd 的 dp 状态定义,而是定义状态 表示从 到 且经过不超过 条边的所有路径所构成的集合中最短的路径长度。
对于任意两点 间的距离,答案就是 ,这是因为题目保证没有负环,因此任意两点间的最短路径不会超过 条边。
根据从 到 的路径中所经过的点 来把路径分成两段,其中前面一段 经过不超过 条边,后面一段 经过不超过 条边。这样就得到状态转移方程:
这种做法的时间复杂度为 。这个时候就可以利用上面的性质来进行优化了。
我们把 看成是矩阵 第 行第 的元素,因此上面的状态转移方程就变成了
矩阵间的运算就是 ,通过递推可以发现有 ,而我们最终要求的矩阵就是 。
这里的 是一个 的矩阵,里面的每一个元素 表示从 到 经过不超过 条边的最短路径。
这里就可以用快速幂来求 的 次方来得到 ,要注意是由 与 所构成的运算规则。
同时根据定义知道矩阵 中任意一个元素 表示 到 不经过任何边的最短路径,因此有
。
这样时间复杂度就降到了 ,虽然还是不如传统的 Floyd 算法,但这种做法给了我们很多启示,比如魔法,智乃酱的双塔问题·极(带修改的DP,DDP),Cow Relays 都是利用这种思想实现的。
做法的 AC 代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 210, INF = 0x3f3f3f3f; 5 6 int n, m, k; 7 int f[N][N], g[N][N], tmp[N][N]; 8 9 void mul(int c[][N], int a[][N], int b[][N]) { 10 memset(tmp, 0x3f, sizeof(tmp)); 11 for (int i = 1; i <= n; i++) { 12 for (int j = 1; j <= n; j++) { 13 for (int k = 1; k <= n; k++) { 14 // 类似于矩阵乘法的c[i][j] = sum(a[i][k] * b[k][j]) 15 // 对于min和+运算有c[i][j] = min{a[i][k] + b[k][j]} 16 tmp[i][j] = min(tmp[i][j], a[i][k] + b[k][j]); 17 } 18 } 19 } 20 memcpy(c, tmp, sizeof(tmp)); 21 } 22 23 int main() { 24 scanf("%d %d %d", &n, &m, &k); 25 // 一开始g=F^0 26 for (int i = 1; i <= n; i++) { 27 for (int j = 1; j <= n; j++) { 28 f[i][j] = g[i][j] = INF; 29 } 30 f[i][i] = g[i][i] = 0; 31 } 32 // 一开始f=F^1 33 while (m--) { 34 int v, w, wt; 35 scanf("%d %d %d", &v, &w, &wt); 36 f[v][w] = min(f[v][w], wt); 37 } 38 m = n - 1; // 快速幂求F^{n-1} 39 while (m) { 40 if (m & 1) mul(g, g, f); 41 mul(f, f, f); 42 m >>= 1; 43 } 44 // 这时有g=F^{n-1} 45 while (k--) { 46 int v, w; 47 scanf("%d %d", &v, &w); 48 if (g[v][w] > INF >> 1) printf("impossible\n"); 49 else printf("%d\n", g[v][w]); 50 } 51 52 return 0; 53 }
参考资料
矩阵乘法笔记:https://www.cnblogs.com/wangruidong03/p/15891893.html
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17272149.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
2022-03-30 翻转树边
2021-03-30 是否同一棵二叉搜索树