[NOIP 2009 提高组] 最优贸易

GitHub跳转原题关系图返回列表

在有向图上同时传播路径最低买价和最大已获利润。

OJ: luogu

题目 ID: P1073

难度:提高

标签:图上 DP最短路队列松弛python

日期: 2026-07-17 03:00

题意

沿 1 到 n 的可重复有向路线,最多买卖一次,求最大利润。

思路

每个节点维护到达它的路径最低价格 minimum 和最大已获利润 profit。沿边传播时,新最低价取较小值,新利润取旧利润与“当前城市售价减此前最低价”的较大值;任一状态改善就重新入队,直到收敛。

Python 知识

  • 两个单调状态数组共同描述到达节点后的最优信息。
  • bytearray 保存是否在队列,避免重复入队。
  • 双向道路按输入类型补反向边。

代码

python
import sys
from collections import deque


input = sys.stdin.buffer.readline
n, roads = map(int, input().split())
price = [0] + list(map(int, input().split()))
graph = [[] for _ in range(n + 1)]
for _ in range(roads):
    x, y, kind = map(int, input().split())
    graph[x].append(y)
    if kind == 2:
        graph[y].append(x)

infinity = 10**9
minimum = [infinity] * (n + 1)
profit = [-1] * (n + 1)
in_queue = bytearray(n + 1)
minimum[1] = price[1]
profit[1] = 0
queue = deque([1])
in_queue[1] = 1
while queue:
    node = queue.popleft()
    in_queue[node] = 0
    for neighbor in graph[node]:
        next_minimum = min(minimum[node], price[neighbor])
        next_profit = max(profit[node], price[neighbor] - minimum[node])
        if next_minimum < minimum[neighbor] or next_profit > profit[neighbor]:
            minimum[neighbor] = min(minimum[neighbor], next_minimum)
            profit[neighbor] = max(profit[neighbor], next_profit)
            if not in_queue[neighbor]:
                queue.append(neighbor)
                in_queue[neighbor] = 1
print(profit[n])

原有 C++ 版本仍保留:

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100005;
const int MAXE = 1000005;
const int INF = 1000000000;

int n, m;
int price_city[MAXN];

int head[MAXN], to[MAXE], nxt[MAXE], edge_cnt;
int rev_head[MAXN], rev_to[MAXE], rev_nxt[MAXE], rev_edge_cnt;

int min_buy[MAXN];  // min_buy[x] 表示从 1 到 x 的某条路线上能遇到的最低价格。
int max_sell[MAXN]; // max_sell[x] 表示从 x 到 n 的某条路线上能遇到的最高价格。
bool in_queue[MAXN];

void add_edge(int u, int v) {
    edge_cnt++;
    to[edge_cnt] = v;
    nxt[edge_cnt] = head[u];
    head[u] = edge_cnt;

    rev_edge_cnt++;
    rev_to[rev_edge_cnt] = u;
    rev_nxt[rev_edge_cnt] = rev_head[v];
    rev_head[v] = rev_edge_cnt;
}

void read_input() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> price_city[i];
    }

    for (int i = 1; i <= m; i++) {
        int x, y, z;
        cin >> x >> y >> z;
        add_edge(x, y);
        if (z == 2) {
            add_edge(y, x);
        }
    }
}

void calc_min_buy() {
    for (int i = 1; i <= n; i++) {
        min_buy[i] = INF;
        in_queue[i] = false;
    }

    queue<int> que;
    min_buy[1] = price_city[1];
    que.push(1);
    in_queue[1] = true;

    while (!que.empty()) {
        int u = que.front();
        que.pop();
        in_queue[u] = false;

        for (int i = head[u]; i != 0; i = nxt[i]) {
            int v = to[i];
            int value = min(min_buy[u], price_city[v]);
            if (value < min_buy[v]) {
                min_buy[v] = value;
                if (!in_queue[v]) {
                    que.push(v);
                    in_queue[v] = true;
                }
            }
        }
    }
}

void calc_max_sell() {
    for (int i = 1; i <= n; i++) {
        max_sell[i] = -INF;
        in_queue[i] = false;
    }

    queue<int> que;
    max_sell[n] = price_city[n];
    que.push(n);
    in_queue[n] = true;

    while (!que.empty()) {
        int u = que.front();
        que.pop();
        in_queue[u] = false;

        for (int i = rev_head[u]; i != 0; i = rev_nxt[i]) {
            int v = rev_to[i];
            int value = max(max_sell[u], price_city[v]);
            if (value > max_sell[v]) {
                max_sell[v] = value;
                if (!in_queue[v]) {
                    que.push(v);
                    in_queue[v] = true;
                }
            }
        }
    }
}

void solve() {
    calc_min_buy();
    calc_max_sell();

    int answer = 0;
    for (int i = 1; i <= n; i++) {
        if (min_buy[i] == INF || max_sell[i] == -INF) {
            continue;
        }
        answer = max(answer, max_sell[i] - min_buy[i]);
    }

    cout << answer << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    read_input();
    solve();

    return 0;
}

复杂度

队列松弛最坏较高,价格仅到 100 时状态下降次数有限;空间 O(n+m)

总结

“先买后卖”沿路径只需记最低历史价格和最大历史差值。