• 温故知新 —— Floyd算法


    什么?Floyd?sb O(n ^ 3) 算法早不用了,右上角红叉吧。
    我之前虽然也认识过 Floyd 算法的重要性,不过多少也是这么想的。
    然而最近三天连续 rand 到了好几道有关的题目,让我彻底重新审视了 Floyd —— 既然能够作为一个重要的算法流传至今,那自有他的重要之处。

    Floyd 是一个求解所有点对间的最短路算法,也可能是绝大多数人接触的最早的最短路算法。它适用于无负权边的图,时间复杂度约为 O(n ^ 3) 。因为时间复杂度太高了,所以也是很多人起初都对它有些成见的原因,再加上任意点对间最短路用的又少,会误认为这个算法后期就一无是处了。
    众所周知,Floyd 的本质是动态规划。对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是就更新它。正是由于这个动态规划思想的精髓所在,以及一层层更新的特性,使得 Floyd 大有用武之地。
    废话不多说了,反正四行的 Floyd 没人不会……

    万恶之源那天,我正在 Luogu 愉快地随机跳题,于是就 rand 到了 P2103 道路值守
    很开心,这不就是个最短路计数?正巧前一天就 A 了一道类似的题,很快就敲出来准备 AC 了。结果嘛……
    怎么办,蒟蒻 Nanjo_Qi 瞬间就没有思路了,于是只能求助题解。
    结果是个用到 Floyd 的题目。

    Floyd 的重要特性是:全面枚举,有序更新。

    Floyd题:https://www.luogu.org/problemnew/show/P2103
    这个也是:https://www.luogu.org/problemnew/show/P1476
    这个也是:https://www.luogu.org/problemnew/show/P1119

    P2103 道路值守:

    利用 Floyd 层层松弛更新,以及循环遍历所有点对的特性,完全无需考虑时间复杂度;
    首先求出所有点对间最短路,然后枚举寻找那些可以作为方案数加入的点(另一条与最短路长度相等,或中途汇入最短路的道路,以此形成可行方案),分别在每个汇入点记录,最后累计。

     1 #include <queue>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <algorithm>
     5 using namespace std;
     6 
     7 const int maxn = 500 + 10;
     8 int n, m, g[maxn][maxn], dis[maxn][maxn], method[maxn][maxn];
     9 
    10 int main(int argc, char const *argv[])
    11 {
    12   memset(dis, 0x3f, sizeof dis);
    13   scanf("%d%d", &n, &m);
    14   for(int i = 1; i <= m; ++i) {
    15     int u = 0, v = 0, w = 0;
    16     scanf("%d%d%d", &u, &v, &w);
    17     dis[u][v] = dis[v][u] = g[u][v] = g[v][u] = w;
    18   }
    19   for(int i = 1; i <= n; ++i) dis[i][i] = 0;
    20   for(int k = 1; k <= n; ++k)
    21     for(int i = 1; i <= n; ++i)
    22       for(int j = 1; j <= n; ++j)
    23         dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
    24 
    25   for(int i = 1; i <= n; ++i) {
    26     int tmp[maxn] = {0};
    27     for(int j = 1; j <= n; ++j) if( i != j && dis[i][j] != dis[0][0] )
    28       for(int k = 1; k <= n; ++k) if( g[k][j] )
    29         if( dis[i][j] == dis[i][k] + g[k][j] ) ++tmp[j];
    30 
    31     for(int j = 1; j <= n; ++j) if( i != j )
    32       for(int k = 1; k <= n; ++k)
    33         if( dis[i][j] == dis[i][k] + dis[k][j] ) method[i][j] += tmp[k];
    34   }
    35 
    36   for(int i = 1; i <= n; ++i)
    37     for(int j = i + 1; j <= n; ++j)
    38       printf("%d ", method[i][j]);
    39 
    40   // printf("_______________________________________________
    ");
    41   // printf("Process Exited Correctly With A Return Value 0.
    ");
    42   // printf("All Rights Reserved By Kimitsu Nanjo In 2018.
    
    ");
    43   return 0;
    44 }
    View Code

    P1476 休息中的小呆:

    求 1 到 n + 1 的最长路;
    用 Dijkstra 不知道为什么跪在了记录路径上,还是用 Floyd 边枚举边输出。

     1 #include <queue>
     2 #include <cctype>
     3 #include <cstdio>
     4 #include <cstring>
     5 #include <algorithm>
     6 using namespace std;
     7 
     8 int n, m, dis[105][105];
     9 
    10 int main(int argc, char const *argv[])
    11 {
    12   scanf("%d%d", &n, &m);
    13   for(int i = 1; i <= m; ++i) {
    14     int u = 1, v = 1, w = 1;
    15     scanf("%d%d%d", &u, &v, &w);
    16     dis[u][v] = w;
    17   }
    18   for(int k = 1; k <= n + 1; ++k)
    19     for(int i = 1; i <= n + 1; ++i)
    20       for(int j = 1; j <= n + 1; ++j)
    21         if( i != j && j != k && dis[i][k] && dis[k][j] )
    22           dis[i][j] = max(dis[i][j], dis[i][k] + dis[k][j]);
    23   printf("%d
    ", dis[1][n + 1]);
    24   for(int i = 1; i <= n + 1; ++i)
    25     if( dis[1][i] + dis[i][n + 1] == dis[1][n + 1] )
    26       printf("%d ", i);
    27 
    28 //   printf("_______________________________________________
    ");
    29 //   printf("Process Exited Correctly With A Return Value 0.
    ");
    30 //   printf("All Rights Reserved By Kimitsu Nanjo In 2018.
    
    ");
    31   return 0;
    32 }
    View Code

    P1119 灾后重建:

    一两个月前做的题,本质是最短路,那时却完全不知道这个时间限制怎么处理(那时的代码还是如此的丑);
    依然是 Floyd,因为这道题保证修复时间和询问都是是递增的,所以就使 k 作为全局变量,每次 k 只以已经修复完成的村庄进行松弛更新,一旦发现村庄 k 的修复时间大于此时的时间,k 就停止自增,等待下一次询问。

     1 #include<cstdio>
     2 #include<cstring>
     3 
     4 int g[210][210], t[210];
     5 int n, m, q, u, v, w, d, k;
     6 inline int min(int a, int b) {
     7     return a>b?b:a;
     8 }
     9 
    10 int main() {
    11     scanf("%d%d", &n, &m);
    12     memset(g, 0x3f, sizeof(g));
    13     memset(t, 0x3f, sizeof(t));
    14     for(int i=0; i<n; ++i) {
    15         scanf("%d", &t[i]);
    16         g[i][i] = 0;
    17     }
    18     for(int i=0; i<m; ++i) {
    19         scanf("%d%d%d", &u, &v, &w);
    20         g[u][v] = g[v][u] = w;
    21     }
    22     
    23     scanf("%d", &q);
    24     for(int i=0; i<q; ++i) {
    25         scanf("%d%d%d", &u, &v, &d);
    26         while( t[k]<=d ) {
    27             for(int i=0; i<n; ++i) {
    28                 for(int j=0; j<n; ++j) {
    29                     g[i][j] = min(g[i][j], g[i][k]+g[k][j]);
    30                 }
    31             }
    32             ++k;
    33         }
    34         if( t[u]>d || t[v]>d || g[u][v]==0x3f3f3f3f ) {
    35             printf("-1
    ");
    36         }
    37         else {
    38             printf("%d
    ", g[u][v]);
    39         }
    40     }
    41     return 0;
    42 }
    View Code

    另外,Floyd 还可以用来求最小环,一个环中的最大结点为 k,与他相连的两个点为 i,j,这个环的最短长度为 g[i][k] + g[k][j] + dis[i][j](i 到 j 的路径中,所有结点编号都小于 k 的最短路径长度)。根据 Floyd 的原理,在最外层循环做了 k-1 次之后,dist[i][j] 则代表了 i 到 j 的路径中,所有结点编号都小于 k 的最短路径。故该算法一定能找到图中最小环。代码如下:

     1 void floyd() {
     2   for(int k = 1; k <= n; ++k) {  // 求最小环,不包含第k个点
     3     for(int i = 1; i < k; ++i) {    // 到k-1即可
     4       for(int j = i + 1; j < k; j++)  // 到k-1即可
     5         mincircle = min(mincircle , dis[i][j] + g[i][k] + g[k][j]);    //无向图 
     6     }
     7 
     8     for(int i = 1; i <= n; ++i)  // 更新最短路
     9       for(int j = 1; j <= n; ++j)
    10           dis[i][j] = min(dis[i][k] + dis[k][j] , dis[i][j]);
    11   }
    12 }
    View Code

    Floyd 还有很多用途,限于篇幅不再赘述了。

                             —— 还记得那天的景色,就像真的到达了那个世界。

  • 相关阅读:
    从零基础到精通的前端学习路线
    js点击弹出网页弹框
    JS实现在线统计一个页面内鼠标点击次数-刷新过后也会保留上次点击次数
    Input文本框随着输入内容多少自动延伸有-bug
    关于Java 后台分页
    关于ECMAScript6 的学习01-ES6 的六种变量声明方式===关于常量const
    关于ECMAScript6 的学习01-ES6 的六种变量声明方式===关于块级变量 let
    接上篇 checkbox数据的带入
    关于checkbox 样式的改变 设置自定义样式
    关于文件上传-图片上传
  • 原文地址:https://www.cnblogs.com/nanjoqin/p/9211982.html
Copyright © 2020-2023  润新知