PAT 1018 Public Bike Management (30分) Dijstra + DFS

Scroll Down

题目

There is a public bike service in Hangzhou City which provides great convenience to the tourists from all over the world. One may rent a bike at any station and return it to any other stations in the city.

The Public Bike Management Center (PBMC) keeps monitoring the real-time capacity of all the stations. A station is said to be in perfect condition if it is exactly half-full. If a station is full or empty, PBMC will collect or send bikes to adjust the condition of that station to perfect. And more, all the stations on the way will be adjusted as well.

When a problem station is reported, PBMC will always choose the shortest path to reach that station. If there are more than one shortest path, the one that requires the least number of bikes sent from PBMC will be chosen.

在这里插入图片描述

The above figure illustrates an example. The stations are represented by vertices and the roads correspond to the edges. The number on an edge is the time taken to reach one end station from another. The number written inside a vertex S is the current number of bikes stored at S. Given that the maximum capacity of each station is 10. To solve the problem at S​3 , we have 2 different shortest paths:
PBMC -> S​1​​ -> S​3​​ . In this case, 4 bikes must be sent from PBMC, because we can collect 1 bike from S​1​​ and then take 5 bikes to S​3​​ , so that both stations will be in perfect conditions.PBMC -> S​2​​ -> S​3​​ . This path requires the same time as path 1, but only 3 bikes sent from PBMC and hence is the one that will be chosen.

Input Specification:
Each input file contains one test case. For each case, the first line contains 4 numbers: C​max (≤100), always an even number, is the maximum capacity of each station; N (≤500), the total number of stations; S​p​​ , the index of the problem station (the stations are numbered from 1 to N, and PBMC is represented by the vertex 0); and M, the number of roads. The second line contains N non-negative numbers C
​i​​ (i=1,⋯,N) where each Ci​​ is the current number of bikes at S​i​​ respectively. Then M lines follow, each contains 3 numbers: S​i​​ , S​j​​ , and T​ij which describe the time T​ij​​ taken to move betwen stations S​i​​ and S​j​​ . All the numbers in a line are separated by a space.

Output Specification:
For each test case, print your results in one line. First output the number of bikes that PBMC must send. Then after one space, output the path in the format: 0−>S
​1​​ −>⋯−>S​p​​ . Finally after another space, output the number of bikes that we must take back to PBMC after the condition of S​p​​ is adjusted to perfect.

Note that if such a path is not unique, output the one that requires minimum number of bikes that we must take back to PBMC. The judge's data guarantee that such a path is unique.

Sample Input:
10 3 3 5
6 7 0
0 1 1
0 2 1
0 3 3
1 3 1
2 3 1
Sample Output:
3 0->2->3 0

中文翻译

杭州市设有公共自行车服务,为来自世界各地的游客提供了极大的便利。可以在任何车站租用一辆自行车,然后将其还给城市中的其他车站。

公共自行车管理中心(PBMC)不断监视所有站点的实时容量。如果工作站刚满一半,则称其处于理想状态。如果一个车站已满或空了,PBMC会收集或派送自行车以将该车站的状况调整到最佳状态。而且,途中的所有车站也会调整到完美状态。

当报告问题站点时,PBMC将始终选择到达该站点的最短路径。如果最短路径多于一条,则将选择需要从PBMC发送的自行车数量最少的那条路径。

思路分析

  • 首先,遇到最短路径,肯定是Dijstra,但题目说了,可能会有多条最短路径,我们要找到全部路径怎么办?把每一个路径都保存下来吗?可以,但没必要。我们只需要记录最短路径上每个节点的直接前驱就可以了,而这个,在Dijstra算法中,只需要在更新过程中(d[v] = d[u] + e[u][v])加上一条语句就可以了(prev[v].push_back(u)),非常简单。如果它有多个最短路径,那么它就有多个直接前驱,==因此,每个节点都应该有一个vector来保存它的全部最短路径的直接前驱,最终,就是需要一个vector数组。==
  • 找到最短路径显然是不够的,我们还要进行选择,选择PCBC按哪一条路走,需要带过去的自行车最少。所以,我们要遍历全部最短路径,于是dfs就出场了,因为我们保存的是它的直接前驱,所以需要从sp往PCBC去遍历。对于每一个最短路径,模拟一次PCBC需要进行的调整过程(逐个访问节点,并根据它的当前容量计算它达到完美需要的自行车参数),记录需要带过来或带回去的自行车数目,如果更少,则选择这条路径。
  • 有一个好的技巧是,我们的完美状态是cmax/2,刚开始给出了所有站点的当前容量,但我们不直接保存它的当前容量,我们用diff数组保存每个站点当前容量与完美状态的差值,这样,我们很容易根据这个值的正负知道它是缺少自行车,缺少几个?或者是需要自行车,需要几个?当然,这不是我这个菜鸡能想出来的,还是参考了柳婼大神的代码之后改出来的,大神是真的强!!!
  • 所以,总的来说就是,==Dijstra找到全部最短路径,dfs遍历每个最短路径并找到最好的那个==,选择路径的原则就是,距离最短>距离相同但发送的自行车最少>距离相同发送的自行车也相同但带回的自行车最少
  • 这里有个问题就是,如果PCBC最终需要发送自行车,那么带回的自行车就是0,如果PCBC最终需要带回自行车,那么发送的自行车就是0,所以应该只需要一个参数就能搞定,我的代码是参照柳神的代码写的,我试着按这个想法去改了改代码,但是并不能通过,反而改出了bug,然后就放弃了,不知道是我的理解有问题,还是我没改对,如果有成功的小伙伴希望能戳一下我,谢谢啦!

满分代码

我的代码注释已经很详细了,应该都能看明白,我就不解释了,有问题可以在评论指出。

#include <iostream>
#include <algorithm>
#include <climits>
#include <vector>
using namespace std;

// 边距
int edge[501][501];
// 起点到每个点的最短距离
int dis[501];
// 起点到每个点的最短路径可能有多个,同一个点就会有多个前驱,我们用vector保存它的所有前驱(都是最短路径下的前驱)
vector<int> pre[501];
// 各个站点当前容量
// 有个窍门是我们这里不直接保存它的当前容量,而且保存它的完美容量的差,
// 这样我们就能很快知道这个站点 缺少 或者 多出 几辆自行车
int diff[501];

// PCBC需要带去,或带回的最少的自行车数
int min_take = ;
// 对应的那个路径
vector<int> path, temp_path;

// 深度优先遍历,从PCBC到S有一个或多个最短路径,比较每种情况下,PCBC最终带过去或带回去的自行车最少
// 要求沿途经过的所有站点都要调整到完美状态
// 因为我们保存的是前驱,所以我们从s往PCBC遍历,temp_list保存本次路径
void dfs(int sp) {
    // 保存自己
    temp_path.push_back(sp);
    // 路径完整了
    if (sp == 0) {
        // 选择这个路径,PCBC最终需要带回去或带过来几辆自行车
        int temp_take = 0;
        // temp_list里面是倒着的(如:sp 3 2 1 0),所以我们要倒序遍历
        for (int i = temp_path.size() - 1; i >= 0; --i) {
            // 当前站点
            int v = temp_path[i];
            // 当前站点多出自行车
            if (diff[v] > 0) {
                temp_take -= diff[v];
            } else {
                // 当前站点完美或缺少自行车,-diff[i]就是缺的数量
                if (temp_take >= -diff[v]) {
                    // 抵消掉一部分带回去的,达到完美状态
                    temp_take += diff[v]; // diff[i]本身就是负数
                } else {
                    // 不够抵消,自己缺少 -diff[i],要带回去的只有temp_back个,
                    // PCBC还需要带来-diff[i] - temp_back个,temp_back就=0
                    temp_take += (-diff[v] - temp_take);
                }
            }
        }
        // 选择本条路径,PCBC需要带过来的自行车更少一些,选择本条路径
        if (abs(min_take) > abs(temp_take)) {
                path = temp_path;
                min_take = temp_take;
        } 
        // 移除自己,“前呼后应原则”
        temp_path.pop_back();
        return;
    }
    // 多个前驱
    for (int i = 0; i < pre[sp].size(); ++i) {
        // 选择其中一条
        dfs(pre[sp][i]);
    }
    // 移除自己,“前呼后应原则”
    temp_path.pop_back();
    return;
}

int main() {

    // 初始化边距
    fill(edge[0], edge[0] + 501 * 501, INT_MAX);
    // 初始化起点到其他点的最短距离
    fill(dis, dis + 501, INT_MAX);

    // c每个站点容量,n个城市,s待处理的站点,m是m条边的距离
    // 编号从1开始,P编号0表示PCBC
    int c, n, s, m;
    scanf("%d %d %d %d", &c, &n, &s, &m);
    // 各个站点当前容量
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &diff[i]);
        // 计算与完美状态的数量差
        diff[i] = diff[i] - c / 2;
    }
    // m条边
    int from, to, d;
    while (m-- > 0) {
        scanf("%d %d %d", &from, &to, &d);
        edge[from][to] = edge[to][from] = d;
    }
    // Dijstraq求PCBC到每个点的最短距离,并记录前驱
    bool visit[501] = {false};
    // 初始化
    dis[0] = 0;
    // 最短路径
    for (int i = 0; i <= n; ++i) {
        int u = -1, min_dis = INT_MAX;
        for (int j = 0; j <= n; ++j) {
            if (!visit[j] && dis[j] < min_dis) {
                u = j;
                min_dis = dis[j];
            }
        }
        if (u == -1)
            break;
        visit[u] = true;
        for (int v = 0; v <= n; ++v) {
            if (!visit[v] && edge[u][v] != INT_MAX) {
                if (dis[u] + edge[u][v] < dis[v]) {
                    // 更新到v的最短路径
                    dis[v] = dis[u] + edge[u][v];
                    // 更新它的前驱
                    pre[v].clear();
                    pre[v].push_back(u);
                } else if (dis[u] + edge[u][v] == dis[v]) {
                    // 多了一条最短路径就多了一个前驱
                    pre[v].push_back(u);
                }
            }
        }
    }
    // 找到了PCBC到全部节点的最短路径,也找到了这些最短路径上每个节点的一个或多个直接前驱
    // 关注的是从PCBC到s的多个最短路径中,需要从PCBC带过去或带回来的自行车最少的路径,使用dfs
    dfs(s);
    // path保存PCBC选择的最短路径,min_take需要带过来的自行车,min_back需要带回去的自行车
    printf("%d 0", min_take > 0 ? min_take : 0);
    // temp s 3 2 1 0,需要输出 0->1->2->3->s
    for (int i = path.size() - 2; i >= 0; --i)
        printf("->%d", path[i]);
    printf(" %d", min_take > 0 ? 0 : -min_take);

    return 0;
}