图论(4)Floyd算法

一、概述

floyd算法主要作用有:1.找最短路   2.求传递闭包   3.找最小环   4.求出恰好经过k条边的最短路

本文章将介绍floyd求最短路的证明以及以上四个作用的实践。

二、floyd算法求最短路的证明

之前就多次提到过图论与dp问题的联系,floyd算法可以由dp思想来推导

状态表示:d[i,j,k],表示从i点到j点,中间(不包含两头)经过的节点编号不超过k的路径中最短的路径长度。

状态集合:从i点到j点,中间经过节点编号不超过k的所有路径

属性:最短长度

状态计算集合划分:所有不含k号点的路径,所有包含k号点的路径。划分依据是路径选不选k号点

状态转移方程:如果不选k号点,则结果仍为d(k-1,i,j)。如果选择k号点,即在路径中加入k号点。那么怎么样加是能求出最短的那一条呢,答案为从i到k的最短路加上从k到j的最短路。所以状态转移方程为:

d[k,i,j]=min(d[k-1,i,j],d[k-1,i,k]+d[k-1,k,j])

根据我们在优化背包问题时积累的知识,我们知道实际上这个数组在计算第k层的时候只用到了k-1层的信息,即可省略k这个维度。dp顺序为:枚举k,枚举i,枚举j,再状态转移。和我们之前提过的floyd算法求最短路一模一样。

三、例题

1.acw1125牛的旅行

第一眼没我没啥思路,因为n比较小,所以考虑纯纯的暴力做法。

首先思考一下答案组成:如果不连线,最小直径可能是某个连通块里最大的最短路。

如果连线,就直接暴力枚举所有可能连接点i,j。会新生成一条“可能的答案”,即从i点出发到原连通块的最长最短路+i到j+j到原连通块的最长最短路。

floyd算法求最短路,暴力枚举所有点,求出从i点出发到其连通块内的最长的最短路maxd数组。再求maxd的最大值res1。

然后暴力枚举连接i,j。求出产生的最短的“可能答案”res2。

res1为不连边时的直径,如果res1大于res2,则连边后直径不变,答案为res1

如果res1大于res2,则直径变为res2,答案为res2。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#define x first
#define y second
using  namespace std;

const double INF =1e20;
const int N =150;
typedef pair<int,int> PII;

//n很小,可以直接暴力处理出距离第i个点最远的点的距离。
//直径有两种情况 1.不连边,直接就是某个连通块里的直径  
//2.暴力遍历连i,j  连边后直径可能是i,j距离+原连通块内离i最远的距离+原连通块内里j最远的距离。
//最终答案是两种情况中的最大值。

PII q[N];
int n;
double d[N][N],maxd[N];
char g[N][N];


double getdist(PII a,PII b)
{
    double dx=a.x-b.x,dy=a.y-b.y;
    return sqrt(dx*dx+dy*dy);

}

int main()
{
    cin>>n;
    for(int i=0;i<n;i++) cin>>q[i].x>>q[i].y;

    for(int i=0;i<n;i++) cin>>g[i];

    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            if(i!=j)
            {
                if(g[i][j]=='1') d[i][j]=getdist(q[i],q[j]);
                else d[i][j]=INF;
            }

    for(int k=0;k<n;k++)
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
            {
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }

    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            if(d[i][j]<INF)
                maxd[i]=max(maxd[i],d[i][j]);

    double res1=0;
    for(int i=0;i<n;i++) res1=max(res1,maxd[i]);


    double res2=INF;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            if(d[i][j]==INF)
            {
                res2=min(res2,getdist(q[i],q[j])+maxd[i]+maxd[j]);
            }
    printf("%lf",max(res1,res2));

}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4385046/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2.求传递闭包 acwing343排序

传递闭包 O(mn3) 

 题意简述:给出若干关系,求出当前信息可求出的所有关系。。其实就是求传递闭包

根据离散数学知识,对i,j更新n次即为传递闭包,多的不说直接看代码。

如何判断出现矛盾:如果发现:从 x 能到达 y 并且从 y 也能到达 x(即,x 比 y 成绩好并且 y 比 x 成绩好),那就是出现矛盾了。即存在关系(x,x);

题目要求求出从小到大的排序,那我们设置一个求最小值函数,如果i和每个数都有关系,则i是最小值,把i弹出。继续求最小值,求n次即得排序。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N =26;

int n,m;
bool g[N][N],d[N][N];
bool st[N];


void floyd()
{
    memcpy(d,g,sizeof g);
    for(int k=0;k<n;k++)
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                d[i][j] |=d[i][k]&&d[k][j];
}

int check()
{
    for(int i=0;i<n;i++) if(d[i][i]) return 2;

    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            if(i!=j&&!d[i][j]&&!d[j][i])//存在未确定关系
                return 0;
    return 1;
}

char getmin()
{
    for(int i=0;i<n;i++)
        if(!st[i])
        {
            bool flag=true;
            for(int j=0;j<n;j++)
                if(!st[j]&&d[j][i]) //j还未输出 且j<i
                {
                    flag=false;
                    break;
                }
            if(flag)
            {
                st[i]=true;
                return i+'A';
            }
        }

}

int main()
{
    while(cin>>n>>m,n||m)
    {
        memset(g,false,sizeof g);
        int type=0,t;
        for(int i=1;i<=m;i++)
        {
            char str[5];
            cin>>str;
            int a=str[0]-'A',b=str[2]-'A';

            if(!type)
            {
                g[a][b]=1;
                floyd();//求一遍传递闭包
                type=check();//判断是否矛盾或者确定
                if(type) t=i;   
            }
        }
        if(!type) puts("Sorted sequence cannot be determined.");
        else if(type==2) printf("Inconsistency found after %d relations.\n", t);
        else
        {
            memset(st,0,sizeof st);
            printf("Sorted sequence determined after %d relations: ", t);
            for(int i=0;i<n;i++)
                cout<<getmin();
            cout<<"."<<endl;
        }
    }
    return 0;
}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4385357/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

增量算法 O(mn2)

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 26;

int n, m;
bool d[N][N];
bool st[N];

int check()
{
    for (int i = 0; i < n; i ++ )
        if (d[i][i])
            return 2;

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < i; j ++ )
            if (!d[i][j] && !d[j][i])
                return 0;

    return 1;
}

char get_min()
{
    for (int i = 0; i < n; i ++ )
        if (!st[i])
        {
            bool flag = true;
            for (int j = 0; j < n; j ++ )
                if (!st[j] && d[j][i])
                {
                    flag = false;
                    break;
                }
            if (flag)
            {
                st[i] = true;
                return 'A' + i;
            }
        }
}

int main()
{
    while (cin >> n >> m, n || m)
    {
        memset(d, 0, sizeof d);

        int type = 0, t;
        for (int i = 1; i <= m; i ++ )
        {
            char str[5];
            cin >> str;
            int a = str[0] - 'A', b = str[2] - 'A';

            if (!type)
            {
                d[a][b] = 1;
                for (int x = 0; x < n; x ++ )
                {
                    if (d[x][a]) d[x][b] = 1;
                    if (d[b][x]) d[a][x] = 1;
                    for (int y = 0; y < n; y ++ )
                        if (d[x][a] && d[b][y])
                            d[x][y] = 1;
                }
                type = check();
                if (type) t = i;
            }
        }

        if (!type) puts("Sorted sequence cannot be determined.");
        else if (type == 2) printf("Inconsistency found after %d relations.\n", t);
        else
        {
            memset(st, 0, sizeof st);
            printf("Sorted sequence determined after %d relations: ", t);
            for (int i = 0; i < n; i ++ ) printf("%c", get_min());
            printf(".\n");
        }
    }

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/145995/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 3.acwing344观光之旅(求无向图最小环)

 dp思路:和floyd算法类似,按照环上编号最大的点分类。

求每一类的最小值:floyd第k层时,floyd矩阵已知所有点,从i到j只经过1到k-1点的最短路径。

则在此时可求环最小值。

 记录方案:floyd算法求更新的点k

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int d[N][N], g[N][N];  // d[i][j] 是不经过点
int pos[N][N];  // pos存的是中间点k
int path[N], cnt;  // path 当前最小环的方案, cnt环里面的点的数量

// 递归处理环上节点
void get_path(int i, int j) {
    if (pos[i][j] == 0) return;  // i到j的最短路没有经过其他节点

    int k = pos[i][j];  // 否则,i ~ k ~ j的话,递归处理 i ~ k的部分和k ~ j的部分
    get_path(i, k);
    path[cnt ++] = k;  // k点放进去
    get_path(k, j);
}

int main() {
    cin >> n >> m;

    memset(g, 0x3f, sizeof g);
    for (int i = 1; i <= n; i ++) g[i][i] = 0;

    while (m --) {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }

    int res = INF;
    memcpy(d, g, sizeof g);
    // dp思路, 假设k是环上的最大点, i ~ k ~ j(Floyd的思想)
    for (int k = 1; k <= n; k ++) {

        // 求最小环, 
        //至少包含三个点的环所经过的点的最大编号是k
        for (int i = 1; i < k; i ++)  // 至少包含三个点,i,j,k不重合
            for (int j = i + 1; j < k; j ++)  
            // 由于是无向图,
            // ij调换其实是跟翻转图一样的道理
            // 直接剪枝, j从i + 1开始就好了
            // 更新最小环, 记录一下路径
                if ((long long)d[i][j] + g[j][k] + g[k][i] < res) {
                    // 注意,每当迭代到这的时候, 
                    // d[i][j]存的是上一轮迭代Floyd得出的结果
                    // d[i][j] : i ~ j 中间经过不超过k - 1的最短距离(k是不在路径上的)
                    res = d[i][j] + g[j][k] + g[k][i];  
                    cnt = 0;
                    path[cnt ++] = k;  // 先把k放进去
                    path[cnt ++] = i;  // 从k走到i(k固定的)
                    get_path(i ,j);  // 递归求i到j的路径
                    path[cnt ++] = j;  // j到k, k固定
                }

        // Floyd, 更新一下所有ij经过k的最短路径
        for (int i = 1; i <= n; i ++) 
            for (int j = 1; j <= n; j ++)   
                if (d[i][j] > d[i][k] + d[k][j]) {
                    d[i][j] = d[i][k] + d[k][j];  
                    pos[i][j] = k;
            }
    }

    if (res == INF) puts("No solution.");
    else {
        for (int i = 0; i < cnt; i ++) cout << path[i] << ' ';
        cout << endl;
    }

    return 0;
}

作者:Sean今天AC了吗
链接:https://www.acwing.com/solution/content/20140/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

4.floyd算法的边数特性(倍增算法)

设邻接矩阵A,求A的n次幂,易发现a[i][j]为从i到j长度为n条边的路的条数。

类比:设带权矩阵A,求A*A,a[i][j]=min(a[i][k]+a[k][j]) 则为经过了n条边的最短距离。

 本题就是类似做法。

首先tmax=100,所以最多用200个点,先离散化以免超时。

然后快速幂算法求倍增。O(n^3*logN)

#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;

const int N =210;

int k,n,m,S,E;
int g[N][N];
int res[N][N];

void mul(int c[][N],int a[][N],int b[][N])
{
    static int temp[N][N];
    memset(temp,0x3f,sizeof temp);
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                temp[i][j]=min(temp[i][j],a[i][k]+b[k][j]);

    memcpy(c,temp,sizeof temp);
}

void qmi()
{
    memset(res,0x3f,sizeof res);
    for(int i=1;i<=n;i++) res[i][i]=0;

    while(k)
    {
        if(k&1) mul(res,res,g);
        mul(g,g,g);
        k>>=1;
    }
}

int main()
{
    cin>>k>>m>>S>>E;
    memset(g,0x3f,sizeof g);//经过一条边从i到j的最短路  因此i~i是INF
    map<int,int> ids;

    if(!ids.count(S)) ids[S]=++n;
    if(!ids.count(E)) ids[E]=++n;
    S=ids[S],E=ids[E];
    while(m--)
    {
        int a,b,c;
        cin>>c>>a>>b;
        if(!ids.count(a)) ids[a]=++n;
        if(!ids.count(b)) ids[b]=++n;
        a=ids[a],b=ids[b];
        g[a][b]=g[b][a]=min(c,g[a][b]);
    }

    qmi();
    cout<<res[S][E]<<endl;
    return 0;

}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4395120/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值