• 简单的floyd——初学


     前言

    (摘自https://www.cnblogs.com/aininot260/p/9388103.html):

    在最短路问题中,如果我们面对的是稠密图(十分稠密的那种,比如说全连接图),计算多源最短路的时候,Floyd算法才能充分发挥它的优势,彻彻底底打败SPFA和Dijkstra

    在别的最短路问题中都不推荐使用这个算法

    功能:求最短路径   ,求有向图的最小环或者最大环(顶点数>=2),求无向图的最小环(顶点数>=3)。

    最短路径

    //code by virtualtan 2019/2

     1 #include<cstdio>
     2 #include<iostream>
     3 #define INF 200000000
     4 #define MAX 10001 
     5 int n,m,s;
     6 int dis[MAX][MAX];
     7 
     8 inline int read()
     9 {
    10     int x=0,k=1; char c=getchar();
    11     while(c<'0'||c>'9'){if(c=='-')k=-1;c=getchar();}
    12     while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    13     return x*k;
    14 }//快读
    15 int main() {
    16     
    17     n=read(),m=read(),s=read();
    18     for(int i = 1; i <= n; i++) {
    19         for(int j = 1;j <= n; j++) {
    20             dis[i][j] = INF;//先初始化为正无穷 
    21         }
    22     }
    23     for(int i = 1, x, y, val; i <= m; i++) {
    24         scanf("%d%d%d",&x,&y,&val);
    25         dis[x][y] = std::min(dis[x][y], val);//如果有边相连 //可以解决重边 
    26     }//用邻接矩阵存图 
    27     for(int k = 1; k <= n; k++) {//k为中介点,就是一个DP 
    28         for(int i = 1; i <= n; i++) {//i为起点,j为终点 
    29         if(i == k || dis[i][k] == INF) continue;
    30             for(int j = 1;j <= n; j++) {
    31                 if(dis[i][j] > dis[i][k] + dis[k][j])
    32                     dis[i][j] = dis[i][k] + dis[k][j];
    33             }
    34         }
    35     }
    36     dis[s][s] = 0;
    37     for(int i = 1; i <= n; i++)
    38     if(i != s)    printf("%d ",dis[s][i]);
    39     else printf("0 ");
    40 }

    注:判断负环:如果存在u,使dis[u][u] < 0; 则存在负环

    //参考代码&blog(实际上是比我写的好的多的东西)
    https://ksmeow.moe/floyd_warshall/

    注意:

    dis[i][j]实际上是dis[k][i][j], 表示i到j的中间节点(不包括i,j)都在(1,k)时,i到j的最短路
    而又因为每一层都是有上一层决定,不会受这一层影响,所以可以利用滚动数组优化内存空间,将k去除掉

     打印路径:

    传送

    传递闭包

    在有向图中,有时不用关心路径的长度,而只关心两点间是否有通路,则可以用“1” 表示联通, “0”表示不联通,这样预处理少许修改后,再把主程序中改成

     1 d[i][j] = d[i][j] || (d[i][k] && d[i][k]); 

    这样的结果称为有向图的传递闭包

    运用例题:https://vjudge.net/problem/UVA-247

     1 #include<bits/stdc++.h>
     2 #include<map>
     3 using namespace std;
     4 const int MAX = 25 + 9;
     5 
     6 int n,m,tmp;
     7 bool d[MAX][MAX];
     8 map <string ,int> num;//人名代表的编号 
     9 map <int , string > name;//根据编号取人名:用于输出答案 
    10 bool in[MAX];//用于防止联通分量中的人重输出 
    11 
    12 int main() {
    13     while(++tmp) {//换行专用 
    14         scanf("%d%d",&n,&m);
    15         if(n==0 && m==0) break;//用0表示退出标志 
    16         name.clear();//初始化
    17         num.clear(); 
    18         memset(d,0,sizeof(d));//初始化
    19         //多数据输入的题目 每次清空是个好习惯 
    20         if(tmp != 1) printf("
    "); 
    21         printf("Calling circles for data set %d:
    ",tmp);//题目需要 
    22 //        for(int i = 1; i <= n; i++) d[i][i] = 0;//也可以不写,这就是那么个意思,自己不能给自己打电话
    23 
    24         string s1,s2;
    25         int number = 0; //真正申请编号用的 
    26         for(int i = 1; i <= m; i++) {
    27             int x = i*2-1, y = i*2;//错了啦!!(本人一开始以为这是申请编号...)
    28             //谁知道它可能重复输入某些人的名字,如果直接写num[s1]=x,num[s2]=y会导致先前名字的编号被修改,这是不符合题意的 
    29             cin >> s1 >> s2;
    30             if(!num.count(s1)) num[s1] = ++number;
    31             if(!num.count(s2)) num[s2] = ++number;
    32             name[ num[s1] ] = s1; name[ num[s2] ] = s2; 
    33             d[num[s1]][num[s2]]  = 1;//联通 
    34             //吐槽一句:我想num的使命就是这了(方便保存编号,以便后面把编号换成人名 和 联通)... 
    35         }
    36         
    37         for(int k = 1; k <= n; k++) {//求有向图的传递闭包 
    38             for(int i = 1; i <= n; i++) {
    39                 for(int j = 1; j <= n; j++) {
    40                     d[i][j] = d[i][j] || (d[i][k] && d[k][j]);
    41                 }
    42             }
    43         }
    44         
    45         memset(in,0,sizeof(in));
    46         for(int i = 1; i <= n; i++) {//寻找联通的圈,并输出解 
    47             if(!in[i]) {
    48                 cout << name[i] ;
    49                 in[i] = 1;
    50                 for(int j = 1; j <= n; j++) {
    51                     if(d[i][j]==1 && d[j][i]==1 && !in[j]) {//只有当i 与 j互相打电话时(表示联通)才输出 
    52                         cout <<", "<<name[j];
    53                         in[j] = 1; 
    54                     }
    55                 }
    56                 printf("
    ");
    57             }
    58         }
    59     }
    60     return 0; 
    61 }

    最小环:

    参考博客:https://blog.csdn.net/qq_36386435/article/details/77403223

            https://www.cnblogs.com/zzqc/p/6855913.html

           https://blog.csdn.net/yo_bc/article/details/75042688

    无向图的最小环:

    (最大环略......)

    板子: 传送门

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int INF = 99999999;//切记别爆  inf*3即可//程序可能出现3个inf相加
     4 const int MAX = 100+9;
     5 
     6 int n,m;
     7 int dis[MAX][MAX];
     8 int e[MAX][MAX];
     9 
    10 
    11 int main() {
    12     while(cin>>n>>m) {//吐槽一下:杭电的OJ这里要写while(~scanf("%d%d",&n,&m) ) 
    13         for(int i = 1; i <= n; i++) {
    14             for(int j = 1; j <= n; j++) {
    15                 if(i == j) e[i][i] = dis[i][i] = 0;
    16                 else e[i][j] = dis[i][j] = INF;
    17             }
    18         }
    19         int a,b,c;
    20         for(int i = 1; i <= m; i++) {
    21             scanf("%d%d%d",&a,&b,&c);    
    22             if(c < e[a][b]) //有可能重复输入某些边,但它权值又不一样 
    23             e[a][b]=e[b][a] = dis[a][b]=dis[b][a] = c;
    24             //多一个 e数组 的作用在于此: 在松弛的过程中,会破坏掉两点之间是否真的存在边的表示,所有需要多开一个 e
    25             //没有真的存在边的话,e就会是INF 
    26         }
    27         
    28         int ans = INF;
    29         /* 外层循环 k 用于更新最小环ans */
    30         for(int k = 1; k <= n; k++) {
    31             /* 先判断最小环(也就是第一个for),再更新最短路(第二个for)的原因:
    32             会出现重边现象,所以一个环至少有三个点,所以每次先判最小环
    33             因为k必须是未用过的,此时的dis[i][j]是遍历了k-1次的最短路,用完判断了再去更新k对应的最短路。
    34             每次比较dist[i][j]+ e[i][k]+e[k][j]的最小值。k—>i———>j—>k;这样一直保证环是三个点。??? 
    35             */
    36             for(int i = 1; i < k; i++) {//关于这里的"i<k"和下面的"j<k" ,我还看到了另一种写法“i<n,j<n”
    37             //原因不知道是不是这个(希望有人可以指点指点):
    38             /* i循环到k-1的原因:
    39             举例:1->3 ,3->2(只有两条边) 的一个图 
    40             已经用3把dis[1][2]给松弛了,再利用3来求最小环时,算出的ans=dis[1][2]+e[1][3]+e[3][2]
    41             这显然不是一个环...所以我们第一个for里i,j都是只循环到k-1 
    42             这就是重边 ??? 
    43             */
    44             
    45                 for(int j  = i+1; j < k; j++) {
    46                     /*j从i+1开始是因为无向图的对称性质 ???
    47                     */ 
    48                     ans = min(ans , dis[i][j] + e[i][k] + e[k][j]);
    49                 }
    50             }
    51             
    52             for(int i = 1; i <= n; i++) {//松弛操作 更新最短路 
    53                 for(int j = 1;  j <= n; j++) {
    54                     dis[i][j] = min(dis[i][j], dis[i][k]+dis[k][j]);
    55                 }
    56             }
    57         }
    58         if(ans == INF) printf("It's impossible.
    ");
    59         else printf("%d
    ",ans);
    60     }
    61     return 0;
    62     
    63 }

    这个环为:j,k,i...j

    有向图的最小环:

    对于上面代码第43行部分,这个j之所以从i+1开始就可以了是因为无向图的对称性质,而有向图并不具有这个性质,所以需要改动.

    仔细想一想,有向图的最小环其实只要直接跑一遍floyd,然后遍历一遍寻找最小的dis[i][i]即可(所以连dis[i][i] 一起都要初始化为INF哦),因为图是无向的所以不必担心出现重边啊

    例题:传送门

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int MAX = 200+9;
     4 const int INF = 0x3f3f3f3f;
     5 
     6 int n,m;
     7 int w[MAX];//w[i]表示弯道i的时间
     8 int e[MAX][MAX]; /*e[i][j]表示 弯道i到弯道j的最小直道时间 与 起点i的时间和
     9  这样就转化为一个普通的图了(节点无权值)*/
    10 
    11 int main() {
    12     scanf("%d%d",&n,&m);
    13     for(int i = 1; i <= n; i++) {
    14         scanf("%d",&w[i]);
    15     }
    16     for(int i = 1; i <= n; i++) {
    17         for(int j = 1; j <= n; j++) {
    18             e[i][j] = INF;
    19         }
    20     }//貌似这题不用这个也行 
    21     int a,b,c;
    22     for(int i = 1; i <= m; i++) {
    23         scanf("%d%d%d",&a,&b,&c);
    24         e[a][b] = min(e[a][b],c+w[a]); 
    25     }
    26     for(int k = 1; k <= n; k++) {
    27         for(int i = 1; i <= n; i++) {
    28             for(int j = 1; j <= n; j++) {
    29                 e[i][j] = min(e[i][j],e[i][k]+e[k][j]);
    30             }
    31         }
    32     }
    33 //    int ans = INF;
    34 //    for(int i = 1; i <= n; i++) ans = min(ans,e[i][i]) ;
    35 //    if(ans==INF) ans = -1 ; //题目要求:必须经过1号弯道 
    36     printf("%d",e[1][1]==INF?-1:e[1][1]); 
    37     return 0;
    38 }

    其他运用:

    一个小运用

    POJ3660 Cow Conte http://poj.org/problem?id=3660

     1 #include<cstdio>
     2 //题目分析:如果 奶牛能力确定,则赢它的奶牛数 + 输给它奶牛数 == n - 1 
     3 #define MAX 5555
     4 bool a[MAX][MAX];
     5 //a[x][y] == 1 表示 x 与 y 比赛,x胜 
     6 int b[MAX], c[MAX];
     7 //c[i]  表示第i个奶牛赢过的奶牛数 , b[j] 表示输的 
     8 int n,m,ans;
     9 
    10 int main() {
    11     scanf("%d%d",&n,&m);
    12     //初始化 
    13     for(int i = 1, x, y; i <= m; i++) {
    14         scanf("%d%d",&x,&y);
    15         a[x][y] = 1;
    16         b[x]++,c[y]++;
    17     }
    18     for(int k = 1; k <= n; k++) {//用floyd枚举中间点 
    19         for(int i = 1; i <= n; i++) {
    20             for(int j = 1; j <= n; j++) {
    21                 if(a[i][j] == 0 && (a[i][k] == 1) && (a[k][j] == 1) ) {
    22                 // 如果 i 比 k 厉害,k 比 j 厉害, i 自然 比 j 厉害 
    23                 //当 i 和 j 没有 比过赛&& i 比 j厉害,通过枚举中间点,可以确定这个中间点k的b[k],c[k] 
    24                     a[i][j] = 1;
    25                     b[i]++;
    26                     c[j]++;
    27                 }
    28             }
    29         }
    30     } 
    31     for(int i = 1; i <= n; i++) if(b[i] + c[i] == n - 1) ans++;
    32     printf("%d",ans);
    33 }

    收获
    floyd 可以 确定两点之间的大小关系(通过枚举中间点)
    不过要注意条件

    此题 还行

    自己选择的路,跪着也要走完。
  • 相关阅读:
    goroutine
    golang package log
    golang单元测试
    golang 文件操作
    go递归打印指定目录下的所有文件及文件夹
    go语言切片作为函数参数的研究
    go数据类型之基本类型
    结束了
    codeforces358D Dima and Hares【dp】
    codeforces1081G Mergesort Strikes Back【期望dp+脑洞】
  • 原文地址:https://www.cnblogs.com/tyner/p/10701736.html
Copyright © 2020-2023  润新知