AcWing 341. 最優貿易(雙向最短路詳解)

題目描述

C國有 n 個大城市和 m 條道路,每條道路連接這 n 個城市中的某兩個城市。任意兩個城市之間最多隻有一條道路直接相連。這 m 條道路中有一部分爲單向通行的道路,一部分爲雙向通行的道路,雙向通行的道路在統計條數時也計爲1條。C國幅員遼闊,各地的資源分佈情況各不相同,這就導致了同一種商品在不同城市的價格不一定相同。但是,同一種商品在同一個城市的買入價和賣出價始終是相同的。商人阿龍來到C國旅遊。當他得知“同一種商品在不同城市的價格可能會不同”這一信息之後,便決定在旅遊的同時,利用商品在不同城市中的差價賺一點旅費。設C國 n 個城市的標號從 1~n,阿龍決定從1號城市出發,並最終在 n 號城市結束自己的旅行。在旅遊的過程中,任何城市可以被重複經過多次,但不要求經過所有 n 個城市。阿龍通過這樣的貿易方式賺取旅費:他會選擇一個經過的城市買入他最喜歡的商品——水晶球,並在之後經過的另一個城市賣出這個水晶球,用賺取的差價當做旅費。因爲阿龍主要是來C國旅遊,他決定這個貿易只進行最多一次,當然,在賺不到差價的情況下他就無需進行貿易。現在給出 n 個城市的水晶球價格,m 條道路的信息(每條道路所連接的兩個城市的編號以及該條道路的通行情況)。請你告訴阿龍,他最多能賺取多少旅費。

輸入格式

第一行包含 2 個正整數 n 和 m,中間用一個空格隔開,分別表示城市的數目和道路的數目。

第二行 n 個正整數,每兩個整數之間用一個空格隔開,按標號順序分別表示這 n 個城市的商品價格。

接下來 m 行,每行有 3 個正整數,x,y,z,每兩個整數之間用一個空格隔開。

如果z=1,表示這條道路是城市 x 到城市 y 之間的單向道路;如果z=2,表示這條道路爲城市 x 和城市 y 之間的雙向道路。

輸出格式

一個整數,表示答案。

數據範圍

1≤n≤100000,
1≤m≤500000,
1≤各城市水晶球價格≤100

輸入樣例

5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2

輸出樣例

5

解題思路

首先本題題意爲:從節點1出發到一個價格最低的節點購買水晶球,再去一個價格最高的節點賣出水晶球,前提是買賣兩個節點使可以到達的。因此可以考慮動態規劃的思路,我們假設買賣由節點k分隔,即在節點1-k購買,在節點k-n賣出,當然也可以不買不賣,那麼有:

  • 從 1 走到 i 的過程中,買入水晶球的最低價格 dmin[i];
  • 從 i 走到 n 的過程中,賣出水晶球的最高價格 dmax[i];

那麼顯然最大差值爲max(dmax[i] - dmin[i]),接下來考慮算法實現。求dmax[i] 和dmin[i]的過程顯然使用最短路算法,由於這次求的最值是節點的權值最值,數值不累計,因此不能使用Dijkstra算法,於是採用spfa求最值,樸素的實現應該是先求節點1到各個節點最小值,然後分別求節點i到節點n的最大值,這樣就等於執行了n次spfa,這樣複雜度顯然太高,於是考慮反向建圖,即將圖路徑取反,然後求節點n到各個節點的最大距離,這樣節點n到節點i的最大距離,就等於原圖中節點i到節點n的最大距離,這樣只需要執行兩次spfa,節省了很多時間,可參考下邊代碼實現,代碼參考yxc。

代碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 100010, M = 2000010;

int n, m;
int price[N];
int h[N], rh[N], e[M], ne[M], idx;
int dmin[N], dmax[N];
bool st[N];

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

//尋找最小距離,flag來區分正反搜索
void spfa(int *d, int start, int *h, bool flag)
{
    queue<int> q;
    memset(st, 0, sizeof st);

    if (flag) memset(d, 0x3f, sizeof dmin);

    q.push(start);
    st[start] = true;
    d[start] = price[start];

    while (q.size())
    {
        int t = q.front();
        q.pop();
        st[t] = false;

        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            //正搜求最小值,反搜求最大值
            if (flag && d[j] > min(d[t], price[j]) || !flag && d[j] < max(d[t], price[j]))
            {
                if (flag) d[j] = min(d[t], price[j]);
                else d[j] = max(d[t], price[j]);

                if (!st[j])
                {
                    st[j] = true;
                    q.push(j);
                }
            }
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);
    memset(rh, -1, sizeof rh);

    for (int i = 1; i <= n; i ++ ) scanf("%d", &price[i]);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        //add(rh, b, a)等是反向建圖
        add(h, a, b), add(rh, b, a);
        if (c == 2) add(h, b, a), add(rh, a, b);
    }
    
    //分別進行一次spfa
    spfa(dmin, 1, h, true);
    spfa(dmax, n, rh, false);

    //枚舉求最大差值
    int res = 0;
    for (int i = 1; i <= n; i ++ ) res = max(res, dmax[i] - dmin[i]);

    printf("%d\n", res);

    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章