洛谷题单指南-最短路-P1073 [NOIP 2009 提高组] 最优贸易

原题链接:https://www.luogu.com.cn/problem/P1073

题意解读:n个城市,m条单向或者双向道路,每个城市有价格属性,从起点跑到终点过程中,求价格最高和价格最低城市价格差的最大值,要先买后卖,也就是价格低的要在价格高的前面。

解题思路:

一、动态规划

思考买和卖的分界点,前半部分低买,后半部分高卖才是最佳策略。

1、状态定义:

设dmin[i]表示从起点1到i的路径中城市的最低价,dmax[i]表示从终点n到i的路径中城市的最高价

2、状态转移:

由于可能存在环,显然无法通过迭代实现状态转移,可以通过最短路算法实现状态转移,由于都是正权边,采用Dijikstra和SPFA都可以

又因为dmin是从起点跑最短路,dmax是从终点跑最短路,因此需要正向、反向建图

对于dmin的更新,核心逻辑为:

if(d[v] > min(d[u], price[v])) 
  d[v] = min(d[u], price[v])
对于dmax的更新,核心逻辑为:
if(d[v] < max(d[u], price[v]))
  d[v] = max(d[u], price[v])

3、初始化:

dmin初始化为正无穷,dmax初始为负无穷

4、结果:

枚举所有的点i,计算dmin[i] + dmax[i]的最大值。

100分代码:

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

typedef pair<int, int> PII;
const int N = 100005, M = 2000005; //注意正反建图,M边的数量
int h1[N], h2[N], e[M], ne[M], idx; //h1正向建图,h2反向建图
int price[N];
int dmin[N]; //dmin[i]表示从起点到i路径中城市最低价格
int dmax[N]; //dmax[i]表示从终点到i路径中城市最高价格
bool vis[N];
int n, m;

void add(int h[], int a, int b)
{
    e[++idx] = b;
    ne[idx] = h[a];
    h[a] = idx;
}

void spfa(int h[], int s, int d[], int type)
{
    if(type == 1) memset(d, 0x3f, sizeof(dmin)); //type=1表示求dmin
    else memset(d, -0x3f, sizeof(dmax)); //type=2表示求dmax
    d[s] = price[s];
    queue<int> q;
    q.push(s);
    while(q.size())
    {
        int u = q.front(); q.pop();
        vis[u] = false;
        for(int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            if(type == 1 && d[v] > min(d[u], price[v]) || type == 2 && d[v] < max(d[u], price[v]))
            {
                if(type == 1) d[v] = min(d[u], price[v]);
                else d[v] = max(d[u], price[v]);
                if(!vis[v])
                {
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
}

int main()
{
    memset(h1, -1, sizeof(h1));
    memset(h2, -1, sizeof(h2));
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> price[i];
    while(m--)
    {
        int x, y, z;
        cin >> x >> y >> z;
        add(h1, x, y), add(h2, y, x);
        if(z == 2) add(h1, y, x), add(h2, x, y);
    }
    spfa(h1, 1, dmin, 1);
    spfa(h2, n, dmax, 2);
    int ans = 0;
    for(int i = 1; i <= n; i++) ans = max(ans, dmax[i] - dmin[i]);
    cout << ans;
    return 0;
}

二、分层图

建立一个3层的分层图,点i在第x层的编号为i + n * x,n为每层节点个数。

第0层和第1层之间,每个点连一条到下一层该点的边,边权为负的该点价格,表示买;

第1层和第2层之间,每个点连一条到下一层该点的边,边权为正的该点价格,表示卖;

从第0层起点1开始,跑到第2层的终点n + n * 2,最长路即为答案。

注意由于有负权边,必须用SPFA算法。

100分代码:

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

typedef pair<int, int> PII;
const int N = 300005, M = 3200005; //注意建3层图
int h[N], e[M], w[M], ne[M], idx; //h1正向建图,h2反向建图
int price[N];
int dist[N];
bool vis[N];
int n, m;

void add(int a, int b, int c)
{
    e[++idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx;
}

int spfa()
{
    memset(dist, -0x3f, sizeof(dist));
    dist[1] = 0;
    queue<int> q;
    q.push(1);
    while(q.size())
    {
        int u = q.front(); q.pop();
        vis[u] = false;
        for(int i = h[u]; ~i; i = ne[i])
        {
            int v = e[i];
            if(dist[v] < dist[u] + w[i])
            {
                dist[v] = dist[u] + w[i];
                if(!vis[v])
                {
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
    return dist[n + n * 2];
}

int main()
{
    memset(h, -1, sizeof(h));
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> price[i];
    while(m--)
    {
        int x, y, z;
        cin >> x >> y >> z;
        for(int l = 0; l < 3; l++)
        {
            add(x + n * l, y + n * l, 0);
            if(z == 2) add(y + n * l, x + n * l, 0);
        }
    }
    for(int i = 1; i <= n; i++) 
    {
        add(i, i + n, -price[i]); //0到1层间点相连,权值为负价格
        add(i + n, i + n * 2, price[i]); //1到2层间点相连,权值为正价格
    }  

    cout << spfa();

    return 0;
}

 

posted @ 2025-04-11 09:28  hackerchef  阅读(44)  评论(0)    收藏  举报