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

图论综合应用:最短路径优化与正环判定

访客 技术 2026年10月6日 1

聚会往返最短时间分析

在农场网络规划场景中,常需计算节点间的往返最优耗时。假设有 N 个农场编号为 1 到 N,其中 X 号农场举办聚会。已知 M 条单向道路及其通行时长,目标是找出所有牛只从各自所在地出发前往 X 并返回原处的过程中,消耗时间的最大值。

该问题本质上是求解单源最短路的双向组合。对于任意节点 i,其总耗时等于"i 到 X 的最短距离"加上"X 到 i 的最短距离"。由于图是有向的,直接求所有点到 X 的距离效率较低。

解决方案采用两次迪杰斯特拉(Dijkstra)算法:

  1. 正向图搜索:以 X 为起点,计算 X 到达其余各点的最短路径,即返程耗时。
  2. 反向图搜索:构建原图的逆拓扑结构(将所有有向边反转),再次以 X 为起点运行算法。此时算出的 X 到其他点的距离,等价于原图中其他点到 X 的距离,即去程耗时。

遍历所有节点,累加两次的结果并取最大值即可。

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;

const int MAXN = 1005;
const int INF = 0x3f3f3f3f;

struct Edge {
    int target;
    int weight;
};

int totalNodes, totalEdges, hostId;
vector<Edge> graph[MAXN];      // 正向邻接表
vector<Edge> revGraph[MAXN];   // 反向邻接表
int distTo[MAXN], distFrom[MAXN];

// 优先队列优化的 Dijkstra 算法
void computePaths(int startNode, const vector<Edge> graph[], int result[]) {
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    
    fill(result, result + totalNodes + 1, INF);
    result[startNode] = 0;
    pq.push({0, startNode});
    
    while (!pq.empty()) {
        int d = pq.top().first;
        int u = pq.top().second;
        pq.pop();
        
        if (d > result[u]) continue;
        
        for (const auto& edge : graph[u]) {
            if (result[edge.target] > d + edge.weight) {
                result[edge.target] = d + edge.weight;
                pq.push({result[edge.target], edge.target});
            }
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    if (!(cin >> totalNodes >> totalEdges >> hostId)) return 0;
    
    for (int i = 0; i < totalEdges; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        graph[u].push_back({v, w});
        revGraph[v].push_back({u, w}); // 构建反向图
    }
    
    // 计算从 X 出发的最短距离(返程时间)
    computePaths(hostId, graph, distFrom);
    // 计算从 X 出发在反向图中的距离(等同于去往 X 的时间)
    computePaths(hostId, revGraph, distTo);
    
    int maxTotalTime = 0;
    for (int i = 1; i <= totalNodes; ++i) {
        if (distFrom[i] != INF && distTo[i] != INF) {
            maxTotalTime = max(maxTotalTime, distFrom[i] + distTo[i]);
        }
    }
    
    cout << maxTotalTime << endl;
    return 0;
}

货币交易套利判定

另一种常见的图论模型涉及资金流转与汇率变动。给定若干种货币及它们之间的兑换规则(包含汇率和手续费),需要判断是否存在一种兑换序列,使得最终持有金额高于初始金额。若存在此类环路,则意味着可以通过无限循环兑换获取无限财富。

这属于最长路问题中的正环检测变种。传统的负权回路检测通常用于寻找最小值陷入死循环的情况,而此处是寻找最大值增长的正反馈回路。我们可以使用 SPFA(队列优化的 Bellman-Ford)算法进行判定。

核心逻辑在于松弛操作:如果通过某种货币兑换能增加当前持有的钱数,则更新距离数组。统计每个节点的入队次数,若某节点被更新次数超过节点总数 N,说明图中存在可以让价值不断增大的正环。

#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <cmath>

using namespace std;

const int MAX_NODES = 110;

struct Transaction {
    int to;
    double rate;
    double commission;
};

int n, m, startCurrency;
double initialCapital;
vector<Transaction> adj[MAX_NODES];
double currentVal[MAX_NODES];
int updateCount[MAX_NODES];
bool inQueue[MAX_NODES];

bool detectProfitLoop() {
    queue<int> q;
    memset(inQueue, false, sizeof(inQueue));
    memset(updateCount, 0, sizeof(updateCount));
    memset(currentVal, 0, sizeof(double) * (n + 1));
    
    currentVal[startCurrency] = initialCapital;
    q.push(startCurrency);
    inQueue[startCurrency] = true;
    
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        inQueue[u] = false;
        
        for (const auto& trans : adj[u]) {
            int v = trans.to;
            // 计算兑换后的新金额:(当前金额 - 手续费) * 汇率
            double newVal = (currentVal[u] - trans.commission) * trans.rate;
            
            if (newVal > currentVal[v] + 1e-8) {
                currentVal[v] = newVal;
                updateCount[v]++;
                
                // 若更新次数达到节点数,存在正环
                if (updateCount[v] >= n) return true;
                
                if (!inQueue[v]) {
                    q.push(v);
                    inQueue[v] = true;
                }
            }
        }
    }
    return false;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    while (cin >> n >> m >> startCurrency >> initialCapital) {
        for (int i = 0; i <= n; ++i) adj[i].clear();
        
        for (int i = 0; i < m; ++i) {
            int u, v;
            double r_uv, c_uv, r_vu, c_vu;
            cin >> u >> v >> r_uv >> c_uv >> r_vu >> c_vu;
            
            adj[u].push_back({v, r_uv, c_uv});
            adj[v].push_back({u, r_vu, c_vu});
        }
        
        if (detectProfitLoop()) {
            cout << "YES\n";
        } else {
            cout << "NO\n";
        }
    }
    
    return 0;
}

相关文章

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 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

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...

发表评论

访客

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