1. 程式人生 > 實用技巧 >【最短路+區間列舉】昂貴的聘禮 POJ - 1062

【最短路+區間列舉】昂貴的聘禮 POJ - 1062

昂貴的聘禮 POJ - 1062

題意:

原題幹說得不清不楚的……坑死我了。

  • 探險家要得到物品\(i\),方式有兩種:一、花費金幣\(P[i]\)直接買;二、先得到指定物品\(X\),然後可以優惠價格\(V\)買得。
  • 每個物品都有地位等級。
  • 給定地位等級差距限制\(M\),表示可行的交易序列中最高地位等級與最低地位等級之差應≤\(M\),超出此範圍時不可交易。如\(M=2\)時,若要買到地位等級為\(4\)的物品,則交易序列中的物品等級只能為以下範圍:\(\{2,3,4\}或\{3,4,5\}或\{4,5,6\}\)
  • 探險家的最終目的是得到物品\(1\)。問他要花費的最少金幣數。
  • 多組資料輸入

思路:

把每個物品視為圖的節點。通過物品\(X\)以優惠價格\(V\)買到物品\(i\),可視為從節點\(X\)出發,連線到節點\(i\),邊權為\(V\)的單向邊。原題目即最短路問題。

在選定起點求最短路時,注意以下幾點:

1.起點本身是要花錢的,而且這個價格不應該被最短路計算改變。

2.選定起點後求得的最短路所經過的所有節點,其地位等級都應滿足差距限制M。

3.物品1是可以直接花錢買的,其他起點求出的最短路要和這個價格再比較一下。

要實現第2點,最簡單的就是從物品\(1\)的地位等級出發,列舉可行的等級範圍。再從這個等級範圍裡列舉所有可行起點計算最短路,並且注意最短路里經過的所有節點都要滿足這個等級範圍。

資料範圍小,dijkstra、SPFA、floyd全都可以過,這裡用最簡潔的floyd。

const int INF = 1e9;
const int maxn = 100 + 10;

struct node {
    int n;
    int level;
};

int d[maxn][maxn];
int d_copy[maxn][maxn];
int n, m;
int up, down;
int level[maxn];
node Node[maxn];


void floyd(int begin,int end) {
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (i == j) continue;
              //d[i][i]是直接買物品i的價格,這是固定的,不能改變
                if (d_copy[i][k] + d_copy[k][j] < d_copy[i][j]) {
                    if (level[i]<begin || level[i]>end) continue;
                    if (level[j]<begin || level[j]>end) continue;
                    if (level[k]<begin || level[k]>end) continue;
                  //i,j,k節點都要在等級範圍內,才能鬆弛
                    d_copy[i][j] = d_copy[i][k] + d_copy[k][j];
                }
            }
        }
    }
}

int main()
{
    // ios::sync_with_stdio(false);
    /// int t; cin >> t; while (t--) {
    int limit;
    //這是一個可滑動的視窗,不是以酋長等級為中位數的固定範圍……
    while (cin >> limit >> n) {
        int ans = INF;
        int pos = 0;
        int L = 0;
        memset(level, 0, sizeof(level));
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                d[i][j] = INF;
                d_copy[i][j] = INF;
            }
        }
        for (int i = 1; i <= n; i++) {
            int value, lev, sub;
            cin >> value >> lev >> sub;
            d[i][i]=d_copy[i][i] = value;
            if (i == 1) L = lev;
            node t;
            t.n = i; t.level = lev;
            Node[++pos] = t;
            level[i] = lev;
            for (int j = 1; j <= sub; j++) {
                int from, dis;
                cin >> from >> dis;
                d[from][i] = d_copy[from][i]= dis;
            }
        }

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (d[i][j] < INF && abs(level[i] - level[j])>limit) {
                    d[i][j] = d_copy[i][j] = INF;
                }
            }
        }

        up = L + limit;
        down = L - limit;
        if (down < 0) down = 0;
        //題目裡說了地位等級都是非負整數

        //列舉等級區間
        for (int i = 0; i+down <= L; i++) {
            int begin = down + i;
            int end = begin + limit;
            //確定等級下限和等級下限
            floyd(begin, end);
            //據此計算最短路,注意i,j,k均要在此等級範圍內(尤其是k!不要忽略了)
            for (int j = 2; j <= n; j++) {
                if (level[j]<begin || level[j]>end) continue;
                ans = min(d_copy[j][j]+d_copy[j][1], ans);
            }
            //列舉起點的時候也要在等級範圍內
            //不是列舉等級啊!!一開始寫成了(int j=begin;j<=end;j++) 太弱智了
            //記得加上買起點的費用!!

            for (int j = 1; j <= n; j++) {
                for (int k = 1; k <= n; k++) {
                    d_copy[j][k] = d[j][k];
                }
            }
            //一定要把算出來的最短路清空掉!!還原回去!!才能計算下一個區間!!
        }

        ans = min(d[1][1], ans);
        //一定要把最後的答案再與直接買酋長禮物的價格再比較一下!!有時候直接買會更便宜

        cout << ans << endl;
        // }
    }
    return 0;
}