题目
在一个公共自行车系统中,有多个自行车点和连接他们的路,现在给出一点V,要求求出从公共自行车调度中心到达V最短的路径,如果不唯一,就找出需要带出最少自行车数量的路径,如果仍然不唯一,就找出最少带回自行车数量的路径。
错误的操作
一开始,只注意到了一条路径上的问题,并未考虑到这个题不能单纯的用Dijskra算法,只用Dijkstra算法可以过前两个点,有一半分(可能就是送的。。。这题的核心还是在后面)
void Dijkstra(int s) {
fill(d, d + MAX_V, INF);
d[s] = 0;
S[s] = store[s];
for (int i = 0; i < N; i++) {
int min = INF, u = -1;
for (int j = 0; j < N; j++) {
if (min > d[j] && visit[j] == false) {
u = j;
min = d[j];
}
}
if (u == -1) return;
visit[u] = true;
for (int j = 0; j < Adj[u].size(); j++) {
int v = Adj[u][j].v;
if (visit[v] == false) {
if (d[u] + Adj[u][j].dis < d[v]) {
d[v] = d[u] + Adj[u][j].dis;
S[v] = S[u] + store[v];
pre[v] = u;
}
else if (d[v] == d[u] + Adj[u][j].dis && S[u] + store[v] > S[v]) {
S[v] = S[u] + store[v];
pre[v] = u;
}
}
}
}
}
代码和思路
- 这个题目,有三个判断条件,条件不能累计,不具有最优子结构,不能直接使用Dijkstra算法。
- 不能使用直接使用Dijkstra算法,就需要将所有的最短路先记录下来,使用pre数组记录前驱,回溯可以记录所有的最短路了。回溯的过程就是DFS,在下面的代码中会给每一步的具体解释。
对代码每部分的详细解释
- 对Dijkstra的优化
- 当最短路径可以更新的时候,则之前的所有点距离一定是远的,可以直接清空,把这个中介点u压进去
- 如果最短路径相同,则把中介点u直接跟在pre[]数组里。
void Dijsktra(int s) { fill(d, d + MAXV, INF); d[s] = 0; for (int i = 0; i < n; i++) { int u = -1, MIN = INF; for (int j = 0; j < n; j++) { if (vis[j] == false && d[j] < MIN) { u = j; MIN = d[j]; } } if (u == -1) return; //和s不连通 vis[u] = true; for (int v = 0; v <= n; v++) { if (vis[v] == false && G[u][v] != INF) { if (d[v] > d[u] + G[u][v]) { d[v] = d[u] + G[u][v]; //u是当前唯一最短路,所以要更新v的前驱,之前的全部清空,然后把u压进去 pre[v].clear(); pre[v].push_back(u); } else if(d[v] == d[u] + G[u][v]) { //u不是当前唯一最短路,直接u压进数组 pre[v].push_back(u); } } } }
}
- DFS
伪代码
这就是个深搜的过程,先把当前结点进数组,然后递归,直到搜到了根节点0,然后计算这条路径上的数据,计算完毕后,把路径上当前最后一个点弹出(就是一个恢复的过程)。
for循环结束后,证明这个v节点的后续已经结束了,pop这个v节点(恢复)。DFS(){ if(v == 0){ 递归出口, 从后往前遍历,把需要送出的车和需要拿回的车记录下来。 比较更新最终值 弹出 return } v进数组 for(v的所有前驱){ DFS(前驱) } 把数组最后一个值弹出 }
具体实现
void DFS(int v) { if (v == 0) { //出口,其实就是深搜完这条最短路径了,可以进行计算。 tempPath.push_back(v); int need = 0, remain = 0; for (int i = tempPath.size() - 1; i >= 0; i--) { int id = tempPath[i]; if (weight[id] > 0) { remain += weight[id]; } else { if (remain > abs(weight[id])) { remain -= abs(weight[id]); } else { need += abs(weight[id]) - remain; remain = 0; } } } if (need < minNeed) { minNeed = need; minRemain = remain; path = tempPath; } else if(need == minNeed && remain < minRemain){ minNeed = need; minRemain = remain; path = tempPath; } //这条路径已经读完,回到根节点前一个点继续递归深入 tempPath.pop_back(); return; } tempPath.push_back(v); for (int i = 0; i < pre[v].size(); i++) { DFS(pre[v][i]); } //回溯结束,已经回到了v,把v弹出 tempPath.pop_back(); }
完整AC代码
#include<cstdio>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;
const int MAXV = 510;
const int INF = 100000;
//n为顶点数, m为边数, Cmax为车站最大承载量, sp为问题站
//weight为点权,使用邻接矩阵
//minNeed记录最少携带数目,minRemain记录最少带回数目
int n, m, Cmax, Sp, numPath = 0, G[MAXV][MAXV], weight[MAXV];
int d[MAXV], minNeed = INF, minRemain = INF;
bool vis[MAXV] = { false };
vector<int> pre[MAXV]; //记录每个节点在最短路情况下的前驱(有可能有多个)
vector<int> tempPath, path; //临时路径和最优路径
void Dijsktra(int s) {
fill(d, d + MAXV, INF);
d[s] = 0;
for (int i = 0; i < n; i++) {
int u = -1, MIN = INF;
for (int j = 0; j < n; j++) {
if (vis[j] == false && d[j] < MIN) {
u = j;
MIN = d[j];
}
}
if (u == -1) return; //和s不连通
vis[u] = true;
for (int v = 0; v <= n; v++) {
if (vis[v] == false && G[u][v] != INF) {
if (d[v] > d[u] + G[u][v]) {
d[v] = d[u] + G[u][v];
//u是当前唯一最短路,所以要更新v的前驱,之前的全部清空,然后把u压进去
pre[v].clear();
pre[v].push_back(u);
}
else if(d[v] == d[u] + G[u][v]) { //u不是当前唯一最短路,直接u压进数组
pre[v].push_back(u);
}
}
}
}
}
void DFS(int v) {
if (v == 0) {
tempPath.push_back(v);
int need = 0, remain = 0;
for (int i = tempPath.size() - 1; i >= 0; i--) {
int id = tempPath[i];
if (weight[id] > 0) {
remain += weight[id];
}
else {
if (remain > abs(weight[id])) {
remain -= abs(weight[id]);
}
else {
need += abs(weight[id]) - remain;
remain = 0;
}
}
}
if (need < minNeed) {
minNeed = need;
minRemain = remain;
path = tempPath;
}
else if(need == minNeed && remain < minRemain){
minNeed = need;
minRemain = remain;
path = tempPath;
}
tempPath.pop_back();
return;
}
tempPath.push_back(v);
for (int i = 0; i < pre[v].size(); i++) {
DFS(pre[v][i]);
}
tempPath.pop_back();
}
int main() {
scanf_s("%d%d%d%d", &Cmax, &n, &Sp, &m);
int u, v;
fill(G[0], G[0] + MAXV * MAXV, INF);
for (int i = 1; i <= n; i++) {
scanf_s("%d", &weight[i]);
weight[i] -= Cmax / 2;
}
for (int i = 0; i < m; i++) {
scanf_s("%d%d", &u, &v);
scanf_s("%d", &G[u][v]);
G[v][u] = G[u][v];
}
Dijsktra(0);
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);
}
总结
这是一个Dijkstra + DFS的过程
主要学到了如何用pre二维数组进行深搜最短路。
最短路pre数组最后其实就是一个确定了首尾的树
注
代码来自于算法笔记,对于代码的具体理解是自己debug一步步得来的。可能有所错误和误解