1018 Public Bike Management (30分)
题目链接:PAT A 1018
题目大意:城市中有一张共享单车图,下标为0的那个点是共享单车管理中心。完美车站是指一个车站拥有的共享单车数目恰好等于车站最大共享单车数的一半。当某个车站车数等于最大单车数或者为零的时候,中心会调整该车站为完美车站,并且将沿途所有车站调整为完美车站。中心将总是选择距离最近的那一条路径去调整问题车站,如果这样的路径有多条,那么就会选择需要中心提供的单车最少的一条路径,如果这样的路径仍然有多条,那么中心将会选择从路径终点带回单车数最少的一条路径,题目将保证这种路径是唯一的。题目输入第一行将给出每个车站的最大共享单车数,车站的数目,问题车站,道路的条数,输入第二行将给出除中心外其他车站现在拥有的共享单车数,接下来将给出各条道路以及距离。
疑难解析:从路径终点带回的单车是什么意思呢?这是指如果沿途的车站所有的单车数比较多,那么到终点,将终点调整为完美车站后也必定会有剩余的单车,这就是需要带回的。
注意:从中心出发到达问题车站的路上就要把所有车站调整完毕,带回时是不调整的!
思路分析:本题根据最短路径,很容易想到使用dijkstra算法去求解,不过光使用dijkstra还不能达到要求,必须结合dfs来解题。由于最短路径有多条,故定义一个vector类型的pre数组来记录前驱结点,例如pre[0]里包含了1,2,就意味着0的前驱结点是1,2,也就是说一个图中由1,2结点可以到达0。在dijkstra算法中,当dis[u] + g[u][v] < dis[v]时,意味着有更短的路径,此时清空pre数组,并且添加进去新的结点,当距离相等时,意味着有多个路径可以到达该结点,只需要添加进去新的结点即可。在dfs算法中定义need与remain分别代表需要从中心支援的单车数和到达某一车站时手上多余的单车数。为了方便起见,在输入各个车站拥有的单车数时就减去最大单车数的一半,这样如果单车数是负值,就意味着需要补充单车,如果是正值,就意味着需要拿掉多余的单车。这样对每一个路径进行统计,最后将找到最优路径。
AC代码:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> pre[510], path, temppath; //前驱结点数组,最优路径,临时路径
const int INF = 0x3fffffff; //一个很大的数
int cmax, n, sp, m, g[510][510], num[510], dis[510];
int minneed = INF, minremain = INF;
bool vis[510] = {false};
void dijkstra() {
fill(dis, dis + 510, INF);
dis[0] = 0;
for(int i = 0; i <= n; i++) { //注意题目中一共有n+1个结点!!
int u = -1, min = INF;
for(int j = 0; j <= n; j++) { //注意题目中一共有n+1个结点!!
if(vis[j] == false && dis[j] < min) { //找出离起始点距离最近的点
u = j;
min = dis[j];
}
}
if(u == -1)
return;
vis[u] = true;
for(int v = 0; v <= n; v++) {
if(vis[v] == false && g[u][v] != INF) {
if(dis[u] + g[u][v] < dis[v]) {
pre[v].clear();
pre[v].push_back(u);
dis[v] = dis[u] + g[u][v];
}
else if(dis[u] + g[u][v] == dis[v])
pre[v].push_back(u);
}
}
}
}
int dfs(int v) {
if(v == 0) {
temppath.push_back(v);
int need = 0, remain = 0;
for(int i = temppath.size() - 1; i >= 0; i--) { //注意一定是倒叙计算,因为pre数组中的顺序是反的
int id = temppath[i];
if(num[id] > 0) //当前站点有多余的单车
remain += num[id]; //带走
else {
if(abs(num[id]) < remain) //单车数不够且缺少的数目小于手里有的数目
remain -= abs(num[id]); //从手里有的中拿
else { //单车数不够且缺少的大于手里有的数目
need = need + abs(num[id]) - remain; //请求中心支援缺少的
remain = 0; //手里有的单车数置零
}
}
}
if(need < minneed) { //优先统计需要中心支援最少的路径
minneed = need;
minremain = remain;
path = temppath;
}
else if(need == minneed && remain < minremain) { //中心支援数目相同且带回数目最少的
minremain = remain;
path = temppath;
}
temppath.pop_back();
}
temppath.push_back(v);
for(int i = 0; i < pre[v].size(); i++)
dfs(pre[v][i]);
temppath.pop_back();
}
int main() {
fill(g[0], g[0] + 510 * 510, INF);
scanf("%d %d %d %d", &cmax, &n, &sp, &m);
for(int i = 1; i <= n; i++) {
scanf("%d", &num[i]);
num[i] -= cmax / 2;
}
for(int i = 0; i < m; i++) {
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
g[a][b] = c;
g[b][a] = c;
}
dijkstra();
dfs(sp);
printf("%d ", minneed);
for(int i = path.size() - 1; i >= 0; i--) {
printf("%d", path[i]);
if(i != 0)
printf("->");
}
printf(" %d", minremain);
return 0;
}
总结:这道题比较有难度,需要熟记dijkstra+dfs算法模板并且对dijkstra+dfs算法有比较深的理解才能解出。预测未来PAT甲级考试关于图的题的难度不会超过此题(近几次考试图的题都比较简单),建议大家多做几遍,好好理解。