题目
https://www.patest.cn/contests/pat-a-practise/1018
题意:自行车管理中心PBMC监控所有站点的实时自行车量,每个站点的最大容纳量都是Cmax,最优状态为Cmax/2。当一个站点为Empty或者Full的时候会向PBMC报告,这时候PBMC需要选择一条到达该站点耗时最短的路径,并且将沿路经过的站点都调整为最优状态。求解这些最短路径中,PBMC出发时携带量最小的路径,若仍有多条,选择带回PBMC量最小的路径。
解题思路
参考https://www.liuchuo.net/archives/2373
膜拜大神系列,代码很清晰,风格很好。
算法
- 先用Dijkstra算法求出单源最短路径,源点即PBMC所在的节点0,在松弛的同时维护每个节点的前驱结点(可能有多个)
- 再从终点开始逆向DFS到节点0,记录这一趟临时路径,再逐个顶点计算这条路径从节点0到终点的携带量和取回量。
- 每趟DFS结束尝试更新最终的路径。
以下几点是题意注意点:
- 只考虑从PBMC到终点这一趟的自行车携带量和取回量
- 沿途过程中可以用之前经过站累加的多余车辆来填补当前站的缺少车辆,但不能用当前站后面的多余车辆来填补,原因是注意点1是单向的。
- 路径选择的标准先是携带量最小,再是带回量最少。
数据结构
Dijkstra的数据结构依然不变,增加的是vector pre[maxv],pre[i]用于记录最短路径上结点i的前驱结点。
为了配合DFS的使用,用diff[maxv]存放每个结点和Cmax/2的差值,多余为正,缺少为负。用tmpPath保存每趟DFS的临时路径,resPath保存最终选择好的路径。同时维护minNeed和minExtra来决定何时用临时路径更新最终路径。
AC代码
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxv = 505, INF = 0x3f3f3f3f;
int cost[maxv][maxv];
int dist[maxv];
bool visited[maxv];
vector<int> pre[maxv]; //记录最短路径上每个结点的前驱结点
int Cmax, dest, n, m;
void init() //初始化
{
for(int i = 0; i <= n; ++i)
for (int j = 0; j <= n; ++j)
{
if (i == j)
cost[i][j] = 0;
else
cost[i][j] = INF;
}
memset(visited, false, sizeof(visited));
}
void dijkstra(int v0) //dijkstra最短路径算法
{
for (int i = 0; i<=n; ++i)
{
dist[i] = cost[v0][i];
if (dist[i] == INF)
pre[i].clear(); //与源点无连接
else
pre[i].push_back(v0); //前驱结点初始化为源点
}
visited[v0] = true;
for (int i = 0; i<n; ++i) //处理剩余n个节点
{
int min_dist = INF, pos; //每次都重新赋值
for (int j = 0; j <= n; ++j)
{
if(!visited[j] && dist[j] < min_dist)
{
pos = j;
min_dist = dist[j];
}
}
visited[pos] = true;
for (int j = 0; j <= n; ++j)
{
if (!visited[j] && dist[pos] + cost[pos][j] < dist[j]) //通过pos到j的路径更短
{
dist[j] = dist[pos] + cost[pos][j];
pre[j].clear(); //删除旧的前驱结点
pre[j].push_back(pos);
}
else if(!visited[j] && dist[pos] + cost[pos][j] == dist[j]) //路径长度相等,加入前驱结点
pre[j].push_back(pos);
}
}
return;
}
int diff[maxv]; //每个站点和Cmax/2的差值,正数表多余,负数表缺少
vector<int> tmpPath, resPath; //存放每次DFS回到源点时的路径,最终的输出路径
int minExtra = INF, minNeed = INF;
void DFS(int v)
{
if (v == 0)
{
tmpPath.push_back(v); //DFS选择
int need = 0, extra = 0; //need代表需要带过去的,extra代表需要带回来的
for (int i = tmpPath.size()-1; i>=0; --i)
{
int id = tmpPath[i];
if (diff[id] > 0) //该站有多余,添加到extra
extra += diff[id];
else if (diff[id] < 0) //该站需要车
{
if (extra > (-diff[id]) ) //累积的多余车足以补充这个站
extra += diff[id]; //加上负数
else //累积量不足以补充这个站
{
need += (-diff[id]) - extra; //出发前还要带着补充这个站的车辆
extra = 0;
}
}
}
if (need < minNeed) //更新最小需要携带的
{
minNeed = need;
minExtra = extra;
resPath = tmpPath; //复制路径序列<终点---起点0>
}
else if(need == minNeed && extra < minExtra) //携带相同时带回来的更少
{
minExtra = extra;
resPath = tmpPath;
}
tmpPath.pop_back(); //DFS回溯
return;
}
//通过v
tmpPath.push_back(v); //DFS选择
//遍历v的前驱结点
for (int i = 0; i < pre[v].size(); ++i)
DFS(pre[v][i]);
//不通过v
tmpPath.pop_back(); //DFS回溯
return;
}
int main()
{
cin >> Cmax >> n >> dest >> m;
diff[0] = 0;
for (int i = 1; i <= n; ++i)
{
cin >> diff[i];
diff[i] -= Cmax >> 1; //存放每个结点和diff的差值
}
int st, ed, c;
init();
for (int i = 0; i < m; ++i)
{
cin >> st >> ed >> c;
cost[st][ed] = cost[ed][st] = c;
}
dijkstra(0);
DFS(dest); //从终点开始DFS
cout << minNeed;
for (int i = resPath.size()-1; i>=0; --i) //注意倒序输出
{
if (i == resPath.size() - 1)
cout << ' ';
else
cout << "->";
cout << resPath[i];
}
cout << ' ' << minExtra << endl;
return 0;
}