1. 程式人生 > 實用技巧 >最優貿易

最優貿易

題目地址

題意

  給定一個圖,找到一條從1~n的路徑,使得路徑上能選出兩個點p,q(先經過p在經過q)並且節點q的節點減去節點p的權值最大。

做法

  1.先以1為起點,跑一遍spfa,求出陣列\(D[x]\)表示從節點 1 到節點 x 的所有路徑中,能過經過的權值最小的節點的權值,\(D[x]\) 與求最短路類似,只需把最短路中用 \(D[x] + w[w,y]\)更新 \(D[y]\) 改成 \(D[y] = min(D[x],price[y])\) 其中\(price[i]\) 表示第i個節點的權值,\(w[x,y]\) 表示最短路中,從x走到y的權值(此處不會用到)。
  2.在以n為起點,在反向圖中,跑一遍spfa,求出陣列\(F[x]\)

這裡表示從節點 n 到節點 x 的所有路徑中,能經過的權值最大的節點的權值,\(F[y] = max(F[x],price[y]\)
  3.最後列舉每個節點x,用\(F[x] - D[x]\)更新答案。

參考程式碼

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

const int N = 100010, M = 500010 * 4;

int h[N],rh[N],e[M],ne[M],idx;
int d1[N],d2[N];
int w[N];
int n,m;
bool st[N];
void add(int h[],int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx ++;
}
// flag: true表示正向圖, false表示反向圖
void spfa(int h[],int dist[],int start,bool flag)
{
    memset(st,false,sizeof st);
    queue<int> q;
    dist[start] = w[start];
    q.push(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 && dist[j] > min(dist[t],w[j])) || (! flag && dist[j] < max(dist[t],w[j])))  // 滿足其中一個就行
            {
                if(flag) dist[j] = min(dist[t],w[j]); // 根據flag的值來賦值。
                else dist[j] = max(dist[t],w[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",&w[i]);

    for(int i = 1; i <= m; i ++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(h,a,b);
        add(rh,b,a); // 建立反向邊
        if(c == 2) add(h,b,a),add(rh,a,b);
    }
    memset(d1,0x3f,sizeof d1); // 求最小值,陣列初始化無窮大
    spfa(h,d1,1,true);         // 第一步求出 D[x],這裡使用d1
    spfa(rh,d2,n,false);       // 第二步求出 F[x],這裡使用d2
    int res = 0;
    // 對應上面第三步
    for(int i = 1; i <= n; i ++){
        //cout << d1[i] << " " << d2[i] << endl;
        res = max(res,d2[i] - d1[i]);![](https://img2020.cnblogs.com/blog/1925085/202010/1925085-20201027210248300-164646963.png)


    }
    cout << res <<endl;
}

特別說明

  本題不能使用dijkstra,因為dijkstra本身就是基於貪心,第一次選取的點即為最小點,如果不是最小點,就會出錯,比如有兩個點:1和2,價格分別是2和1,一共有兩條邊:1->2,和2->1,那麼最初優先佇列中只有一個點1,此時1被彈出,它的最小价格是2,但2並不是最終的最小值。所以dijkstra演算法是不適用的。

為什麼求最大值不能在原圖跑???

  因為並不是所有點都可以到達終點,必須保證,從1走到x號點的同時還要保證x能走到n號點,例如下面:

如果正向求一邊,發現最大值是 F[3] - D[3] = 6 - 1 = 5 ,但是這不滿足要求,因為3最後不能走到4,正確的答案應該是 F[4] - D[4] = 3 - 1 = 2
所以我們需要從反向圖求最大值,即4壓根都走不到3,F[3] = 0,D[3] = 6,我們在求答案的時候 res = max(res,F[3] - D[3]) 是不會更新res的,因為res初始為0。