当前位置:首页 > 技术 > 正文内容

Floyd算法进阶:从图论连通性问题到矩阵快速幂优化

访客 技术 2026年7月13日 6

在近期的一场图论专项模拟赛中,主要围绕Floyd算法及其变体展开,以下是对三道经典题目的深入分析和解题思路复盘。

A. 牧场的旅行:连通块与直径计算

本题源自洛谷 P1522,核心问题是如何连接两个不同牧场的点以最小化新牧场的直径。

关键思路分为三步:首先使用并查集预处理连通块;然后运行Floyd算法计算所有点对之间的最短距离;最后枚举所有可能连接的点对。

定义 mx[i] 为从点i出发到达其连通块内最远点的距离,t[block] 为整个连通块的直径(即块内所有 mx 的最大值)。当连接点i和点j(分属不同连通块)时,新直径取三者最大值:两个原始连通块直径、以及 mx[i] + dis(i,j) + mx[j]

#include <bits/stdc++.h>
using namespace std;
typedef double db;
const int N = 202;
const db INF = 0x3fffffff;
char grid[N][N];
int n, parent[N];
db xs[N], ys[N];
db dist[N][N], maxDist[N], blockDia[N];

db calcDist(int i, int j) {
    return sqrt((xs[i]-xs[j])*(xs[i]-xs[j]) + (ys[i]-ys[j])*(ys[i]-ys[j]));
}

int findRoot(int x) {
    return parent[x] == x ? x : parent[x] = findRoot(parent[x]);
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        parent[i] = i;
        scanf("%lf%lf", &xs[i], &ys[i]);
    }
    for (int i = 1; i <= n; i++) {
        scanf("%s", grid[i] + 1);
        for (int j = 1; j <= n; j++) {
            if (grid[i][j] == '1') {
                dist[i][j] = calcDist(i, j);
                parent[findRoot(i)] = findRoot(j);
            } else if (i != j) dist[i][j] = INF;
        }
    }
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++)
            if (findRoot(i) == findRoot(j)) maxDist[i] = max(maxDist[i], dist[i][j]);
        blockDia[findRoot(i)] = max(blockDia[findRoot(i)], maxDist[i]);
    }
    db result = INF;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (findRoot(i) != findRoot(j)) {
                db newDia = max(maxDist[i] + calcDist(i, j) + maxDist[j],
                                max(blockDia[findRoot(i)], blockDia[findRoot(j)]));
                result = min(result, newDia);
            }
    printf("%.6lf", result);
    return 0;
}

B. 快速米变速:2^k 距离与倍增思想

该题来自洛谷 P1613,关键点在于"2^k"的行驶距离,自然联想到倍增法。

定义状态 can[i][j][k] 表示是否存在一条从i到j长度为2^k的路径。若 can[i][mid][k-1]can[mid][j][k-1] 都成立,则 can[i][j][k] 为真,且此时 dis[i][j] 可设为1。最后通过Floyd求出最短路径数。

#include <bits/stdc++.h>
using namespace std;
const int N = 55, K = 70;
int n, m, u, v;
bool reach[N][N][K];
int graph[N][N];

int main() {
    scanf("%d%d", &n, &m);
    memset(graph, 0x3f, sizeof graph);
    for (int i = 0; i < m; i++) {
        scanf("%d%d", &u, &v);
        reach[u][v][0] = true;
        graph[u][v] = 1;
    }
    for (int k = 0; k < 64; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                for (int mid = 1; mid <= n; mid++)
                    if (reach[i][mid][k] && reach[mid][j][k]) {
                        reach[i][j][k+1] = true;
                        graph[i][j] = 1;
                    }
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j]);
    printf("%d", graph[1][n]);
    return 0;
}

C. 最短路径变体:精确边数约束与矩阵快速幂

本题源自洛谷 P2886,要求恰好经过n条边(n可达10^6)的最短路。由于边数仅100,但顶点编号可达1000,需先离散化。

关键优化:将Floyd看作矩阵乘法。定义矩阵A表示经过x条边的最短路径,矩阵B表示经过y条边的最短路径,则合并后矩阵C表示经过x+y条边的最短路径,公式为 C[i][j] = min(A[i][k] + B[k][j])。通过快速幂思想,只需log(n)次矩阵乘法。

注意:需要转移n-1次而非n次;某些OJ需要输出-1表示无解。

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int n, edgeCnt, src, dst, total;
int mapping[1010];

struct Matrix {
    int data[120][120];
    Matrix operator*(const Matrix &other) const {
        Matrix res;
        memset(res.data, INF, sizeof res.data);
        for (int k = 1; k <= total; k++)
            for (int i = 1; i <= total; i++)
                for (int j = 1; j <= total; j++)
                    res.data[i][j] = min(res.data[i][j],
                                         data[i][k] + other.data[k][j]);
        return res;
    }
} base, answer;

int main() {
    memset(base.data, INF, sizeof base.data);
    scanf("%d%d%d%d", &n, &edgeCnt, &src, &dst);
    while (edgeCnt--) {
        int w, a, b;
        scanf("%d%d%d", &w, &a, &b);
        if (!mapping[a]) mapping[a] = ++total;
        if (!mapping[b]) mapping[b] = ++total;
        int u = mapping[a], v = mapping[b];
        base.data[u][v] = base.data[v][u] = w;
    }
    n--;
    answer = base;
    while (n) {
        if (n & 1) answer = answer * base;
        base = base * base;
        n >>= 1;
    }
    printf("%d", answer.data[mapping[src]][mapping[dst]]);
    return 0;
}

以上三道题展示了Floyd算法在不同场景下的灵活运用:从基础连通块计算,到倍增思想处理2^k约束,再到矩阵快速幂优化大量边数限制。掌握这些变体对深入理解图论算法很有帮助。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

linux screen 用法详情 (nohup 的替代方案)

一、screen 是什么?能干嘛?screen 是一个终端复用器,可以:在一个 SSH 会话中开多个“虚拟终端”SSH 断线后,程序仍然在后台运行随时重新连接到原来的会话特别适合:nohup 的替代方案跑脚本 / 爬虫 / 训练模型运维、远程开发二、安装 screen# CentOS / Rocky / Almayum install -y screen# Debian / Ubuntuapt i...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。