zoukankan      html  css  js  c++  java
  • 简单的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 可以 确定两点之间的大小关系(通过枚举中间点)
    不过要注意条件

    此题 还行

    自己选择的路,跪着也要走完。
  • 相关阅读:
    天梯赛5-12 愿天下有情人都是失散多年的兄妹 【dfs】
    poj2718 Smallest Difference【贪心】
    HDU problem 5635 LCP Array【思维】
    codeforces 782C Andryusha and Colored Balloons【构造】
    HDU 4278 Faulty Odometer【进制转换】
    codeforces B. The Meeting Place Cannot Be Changed【二分】
    POJ 3264 Balanced Lineup 【线段树】
    HDU 1850
    CodeForces-714C
    HDU Problem 1247 Hat's Words 【字典树】
  • 原文地址:https://www.cnblogs.com/tyner/p/10701736.html
Copyright © 2011-2022 走看看