3.2 floyd算法及其扩展应用

总览

在这里插入图片描述

floyd 原理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

AcWing 1140. 最短网络

分析

注意分析题意: 直径最大的牧区连接后直径尽可能短
因此:
1.需要求每个牧场最短距离, 然后计算距离i点的maxd[i]表示当前牧区的直径,
2. 然后计算连接i ~ j 后, 按照题意, 让长度尽可能取短 min(maxd[i] + d[i][j] + maxd[j]) ,因为经过i~j这条边不一定是直径, 因此不一定比maxd[i]大.
所以会有以下情况
3. 与情况1 取max 表示连接后的直径

在这里插入图片描述
在这里插入图片描述

code

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

#define x first
#define y second

using namespace std;

typedef pair<double, double> PDD;

const int N = 155;
const double INF = 1e20;

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

double get_dist(PDD a, PDD b)
{
    double dx = a.x - b.x;
    double 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) d[i][j] = 0;
            else if (g[i][j] == '1') d[i][j] = get_dist(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]);

    double r1 = 0;
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < n; j ++ )
            if (d[i][j] < INF / 2)
                maxd[i] = max(maxd[i], d[i][j]);
        r1 = max(r1, maxd[i]); // 计算直径
    }

    double r2 = INF;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            if (d[i][j] > INF / 2)
                r2 = min(r2, maxd[i] + maxd[j] + get_dist(q[i], q[j])); // 直径最大的两个牧场,
                // 的经过i~j的最小距离
    printf("%.6lf\n", max(r1, r2)); // 直径最大的牧场连接i~j的后的直径

    return 0;
}

传递闭包

a -> b, b->c, 那么我们可以连接a->c, 所有可以间接到达的点, 我们可以连接一条直接的边, 然后把所有这些间接能到的点连上边后, 所形成的图叫做原图的传递闭包
flyod 可以在 O ( n 3 ) O(n^3) O(n3) 将原图 变成传递闭包, 即:g(i, j) -> d(i, j)
在这里插入图片描述
在这里插入图片描述

AcWing 343. 排序

分析

读入当前组的数据, 对当前组的数据, 求一遍传递闭包.
求完传递闭包, 判断下3种情况
在这里插入图片描述
输出次序: 从0开始遍历, 求出当前最小的, 且没有被标记过的数, 并且打上标记.

code

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

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 d);

    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 < 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]) // 存在一个没有被标记过, 且小于i的元素, 说明当前的i不符合要求
                {
                    flag = false;
                    break;
                }
            if (flag)
            {
                st[i] = true;
                return 'A' + i;
            }
        }
}

int main()
{
    while (cin >> n >> m, n || m)
    {
        memset(g, 0, 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) // 只有当前状态不确定type == 0的时候, 才能继续往后做
            {
                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 ++ ) printf("%c", get_min());
            printf(".\n");
        }
    }

    return 0;
}

code(增量算法)

因为每次只会增加1条边, 因此只需要将这条边能够连接到的边, 增加上去即可, 比如加了一条边 i → j i \to j ij, 不需要重新做一遍floyd
只需要看下由这条边, 可以传递到的所有的关系
枚举下, 如果 a → i , j → b a \to i, j\to b ai,jb, 那么 a → b a \to b ab
时间复杂度 O ( n 3 ) → O ( n 2 ) O(n^3) \to O(n^2) O(n3)O(n2)
在这里插入图片描述
注意: 增加这条边的时候, 同时更新 前面的点 能到b的边, 即:
if (d[x][a]) d[x][b] = 1
反过来也是一样
同时也要更新a能到的边
if (d[b][x]) d[a][x] = 1
在这里插入图片描述

#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;
}

344. 观光之旅

分析

按照环上的编号最大的点的编号来分类. 在floyd的第一层循环下, 有d[i][j]数组: 从 i → j i \to j ij, 只经过1 ~ k - 1的点的最短路径
可以发现, 可以用来求第k类
当前状态下, 所有的环都长这样, 因此可以枚举所有的点对i 和 j

在这里插入图片描述

当前固定i 和 j, i → k i \to k ik, k → j k \to j kj的长度固定, 因此想要让整个环, 长度最小, 必须让 j → i j \to i ji最短, 但是这个距离正好是第一重循环到第k层的d[i][j]
因此, 第k类的最小值就是d[i][j] + w[i][k] + w[k][j]
求总的最小, 那么k从1 ~ n循环完后, 取min就可以了
记录方案:
存路径的话, 只需要存在使得d[i][j] >= d[i][k] + d[k][j]的k, pos[i][j] = k, 然后递归处理[i, k], 和[k, j].
在这里插入图片描述

code

#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int d[N][N], g[N][N];
int pos[N][N];
int path[N], cnt;

void get_path(int i, int j){
    if (pos[i][j] == 0) return ;
    
    int k = pos[i][j];
    get_path(i, k);
    path[cnt ++] = 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;
        scanf("%d%d%d", &a, &b, &c);
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    
    int res = INF;
    memcpy(d, g, sizeof d);
    for (int k = 1; k <= n; k ++ ){
        for (int i = 1; i < k; i ++ )
            for (int j = i + 1; j < k; j ++ )
                if ((long long)d[i][j] + g[j][k] + g[k][i] < res){
                    res = d[i][j] + g[j][k] + g[k][i];
                    cnt = 0;
                    path[cnt ++] = k;
                    path[cnt ++] = i;
                    get_path(i, j);
                    path[cnt ++] = j;
                }
        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;
}

AcWing 345. 牛站

分析

编号1~1000, 一共只有100条边, 即200个点, 需要先离散化
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因为两两状态之间是独立的, 因此具有结合律, 所以能用倍增的方法去求解

code

#include <iostream>
#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);
    // res[i][i]就是在快速幂算法中先把res设置成单位元。
    // 整数中的单位元是1
    // 本题中的单位元就是对角线是0,其余元素是正无穷的矩阵。
    // 单位元就是和其他任何元素操作一次,结果都是那个元素本身的元素。	
    // res 与其他任何元素mul 都是其他任何元素
    for (int i = 1; i <= n; i ++ ) res[i][i] = 0;
    
    while (k){
        if (k & 1) mul(res, res, g); // res = res * g;
        mul(g, g, g); // g = g * g;
        k >>= 1;
    }
}

int main(){
    cin >> k >> m >> S >> E;
    
    memset(g, 0x3f, sizeof g);
    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(g[a][b], c);
    }
    
    qmi();
    
    cout << res[S][E] << endl;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值