• 《算法竞赛进阶指南》,USACO2007 牛站


    给定一张由 T 条边构成的无向图,点的编号为 1∼1000 之间的整数。

    求从起点 S 到终点 E 恰好经过 N 条边(可以重复经过)的最短路

    注意: 数据保证一定有解。

    输入格式

    第 11 行:包含四个整数 N,T,S,E。

    第 2..T+1 行:每行包含三个整数,描述一条边的边长以及构成边的两个点的编号。

    输出格式

    输出一个整数,表示最短路的长度。

    数据范围

    2≤T≤100,
    2≤N≤106

    输入样例:

    1. 2 6 6 4
    2. 11 4 6
    3. 4 4 8
    4. 8 4 9
    5. 6 6 8
    6. 2 6 9
    7. 3 8 9

    输出样例:

    10
    
    难度:中等
    时/空限制:1s / 64MB
    总通过数:3516
    总尝试数:5757
    来源:《算法竞赛进阶指南》,USACO2007
    算法标签

    时间复杂度 o(logN * n^3) 

    解题思路:
    定义状态d[a + b][i][j]为从i -> j 恰好经过a + b条边,假设从i -> j的一个中间点k则它可以由

    d[a][i][k] + d[b][k][j]转化而来, 而这两部分分别独立因此可以求得分别的最小值再相加,最后即为恰好经过a + b条边从i走到j的最小值

            

    类比floyd算法:d[k][i][j]:从i -> j经过不大于k的点的编号所走的最短路径。

    与floyd比较我们也可以省去第一维:证明如下:

    (13条消息) floyd 算法模板详解(适合新手)_wsh1931的博客-CSDN博客

    根据上述分析无论是先算i -> k还是先算k -> j的结果均相同,因此它存在结合律,即可以按任意顺序计算,因此可以用矩阵乘法。

    那么如何利用矩阵乘法求出从i -> j恰好经过k条路的最短路径?

    k可以转化为二进制数利用快速幂思想依次求得从i -> j恰好经过1, 2, 4 ...条路的最短路,因为二进制数可以表示为任何数,所以k无论取多少它依旧可以取得恰好经过k条边的最小值

    细节:题目中共有1000个点而最多只给100条边即200个点因此可以用离散化来减少要枚举的点 

     

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. const int N = 210;
    7. map<int, int> idx;//离散化数组
    8. int k, m, S, E, n;
    9. int g[N][N], res[N][N];//g为原数组,res为从i -> j经过k条边的最短路
    10. void mul(int c[][N], int a[][N], int b[][N])
    11. {
    12. static int temp[N][N];
    13. memset(temp, 0x3f, sizeof temp);//防止c数组读写混乱
    14. for (int k = 1; k <= n; k ++ )
    15. for (int i = 1; i <= n; i ++ )
    16. for (int j = 1; j <= n; j ++ )
    17. temp[i][j] = min(temp[i][j], a[i][k] + b[k][j]);
    18. //为何它可以从两条边转化为求四条边呢??
    19. //因为a[i][k]表示恰好经过2条边, b[k][j]表示恰好经过2条边
    20. //则他们相加则是恰好经过四条边
    21. memcpy(c, temp, sizeof temp);
    22. }
    23. void qmi()
    24. {
    25. memset(res, 0x3f, sizeof res);
    26. for (int i = 1; i <= n; i ++ ) res[i][i] = 0;//当经过0条边时
    27. //若不能理解res[i][i] = 0可以在mul函数中令i == k会发现上述不等式
    28. //将变为a[i][i] + b[i][j]其中a[i][i]表示经过0条边,b[i][j]表示经过一条边
    29. //他们相加即为经过一条边
    30. while (k)//快速幂模板
    31. {
    32. if (k & 1) mul(res, res, g);
    33. mul(g, g, g);
    34. k >>= 1;
    35. }
    36. }
    37. int main()
    38. {
    39. cin >> k >> m >> S >> E;
    40. memset(g, 0x3f, sizeof g);
    41. //不需要初始化for (int i = 1; i <= N; i ++ ) g[i][j] = 0,
    42. //因为题目说必需经过k条边从i -> i也可能存在边所以不应该初始化
    43. if (!idx.count(S)) idx[S] = ++ n;//离散化
    44. if (!idx.count(E)) idx[E] = ++ n;
    45. S = idx[S], E = idx[E];
    46. while (m -- )
    47. {
    48. int c, a, b;
    49. scanf("%d %d %d", &c, &a, &b);
    50. if (!idx.count(a)) idx[a] = ++ n;
    51. if (!idx.count(b)) idx[b] = ++ n;
    52. a = idx[a], b = idx[b];
    53. g[a][b] = g[b][a] = min(c, g[a][b]);//若遇到重边取最小值
    54. }
    55. qmi();
    56. cout << res[S][E] << endl;
    57. return 0;
    58. }

  • 相关阅读:
    软件测试/测试开发丨Python安装指南(macOS)
    《Effective Java》知识点(4)--泛型
    freertos之任务运行时间统计实验
    sentry安装self-hosted版,前端监控平台
    golang、gin、gorm、casbin访问权限控制
    第十二章 pytorch中使用tensorboard进行可视化(工具)
    【机器学习项目实战10例】(四):利用XGBoost实现短期电力负荷预测
    14:00面试,14:06就出来了,问的问题有点变态。。。
    安全、可靠、合规,华为云守护企业网站安全
    前端异常监控系统
  • 原文地址:https://blog.csdn.net/qq_61935738/article/details/126599745