题目
思路
首先想到用Dijkstra可以求出从原点到目的地的最短路。那么问题在于,在若干条最短路上,怎么确定走哪条路需要发出的车数最少。
我们不能根据某个站点的车数或者该路径上的总车数确定需要发出的车数send和收回的车数back,即发出的车数send和收回的车数back不满足最优子结构,只有在确定了所有最短路之后才能确定send和back的值。
错误示例
用Dijkstra和贪心算法。
用状态转移方程描述发出的车数:
sendNum = max(send[u], (passByNum[u] + 1) * (capacity / 2) - (bikeSum[u] + bikeNum[v])); //当到达v站时,需要发出的车数 = 到达u站需要发出的车数、到达v站需要发出的车数中的较大者
当最短路更新时直接更新send[v] = sendNum
当一条路和最短路长度相等时send[v] = min(sendNum, send[v])
以上仅根据子问题最优解逐步确定整体的最优解的方法是错误的,举个例子:
如果运用贪心算法,当计算的站点到达两条路交汇点,当前最优路径必然为下面的那条路,因为它当前只需要发出2辆车,且只收回的车数也更少。但是当计算继续向右进行,发现后面的站点车数很少,由于下面那条路的总车数比上面那条路的总车数少,最后计算得到的发车数send反而比走上面的那条路的更多,显然贪心算法不能解决这个问题。
错误代码
#include<iostream>
#include<list>
#include<algorithm>
using namespace std;
//Dijkstra + 贪心算法
int main() {
int inf = 999999999;
int map[501][501]; //邻接矩阵
fill(map[0], map[0] + 501 * 501, inf);
bool visited[501] = { false }; //标记访问过的结点
int time[501]; //从0结点到达各个结点的最短用时
fill(time, time + 501, inf);
time[0] = 0;
int bikeNum[501] = { 0 }; //记录点权
int bikeSum[501] = { 0 }; //从原点到达某个站点,经过的站点的车数之和,用来计算发车数、收回车数
int send[501]; //从原点到达某个站点,需要从原点发出的车数
fill(send, send + 501, inf);
send[0] = 0;
int passByNum[501] = { 0 }; //到达某个站点时已经经过的站点数
int pre[501]; //记录最短路中某个结点的上一个结点
int capacity, stationNum, destination, pathNum;
scanf("%d %d %d %d", &capacity, &stationNum, &destination, &pathNum);
for (int i = 1;i <= stationNum;i++) {
scanf("%d", &bikeNum[i]);
}
for (int i = 0;i < pathNum;i++) {
int s1, s2, time;
scanf("%d %d %d", &s1, &s2, &time);
map[s1][s2] = time;
map[s2][s1] = time;
}
for (int i = 0;i <= stationNum;i++) {
//找出下一个"到达用时"最短的站
int u = -1, nextTime = inf;
for (int j = 0;j <= stationNum;j++) {
if (!visited[j] && time[j] < nextTime) {
u = j;
nextTime = time[j];
}
}
if (u == -1)
break;
visited[u] = true;
// next --> j
for (int v = 1;v <= stationNum;v++) {
if (visited[v] || map[u][v] == inf)
continue;
int sentNum = max(send[u], (passByNum[u] + 1) * (capacity / 2) - (bikeSum[u] + bikeNum[v]));
if (time[u] + map[u][v] < time[v]) { //更新最短路
time[v] = time[u] + map[u][v];
bikeSum[v] = bikeSum[u] + bikeNum[v];
pre[v] = u;
passByNum[v] = passByNum[u] + 1;
send[v] = sentNum;
}
else if (time[u] + map[u][v] == time[v]) {
if (send[v] > sentNum || send[v] == sentNum && send[v] + bikeSum[v] - passByNum[v] * (capacity / 2) > sentNum + bikeSum[u] + bikeNum[v] - (passByNum[u] + 1) * (capacity / 2)) { //当前路径需要从原点发出的车数更少,或者发车的车数相等,但要收回的车数更少
pre[v] = u;
bikeSum[v] = bikeSum[u] + bikeNum[v];
passByNum[v] = passByNum[u] + 1;
send[v] = sentNum;
}
}
}
}
list<int> stations;
int pos = destination;
while (pos != 0) {
stations.push_front(pos);
pos = pre[pos];
}
stations.push_front(0);
int temp = 0;
printf("%d ", send[destination]);
for (list<int>::iterator it = stations.begin();it != stations.end();it++) {
if (it != stations.begin())
printf("->");
printf("%d", *it);
}
int back = send[destination] + bikeSum[destination] - passByNum[destination] * (capacity / 2);
back = back < 0 ? 0 : back;
printf(" %d", back);
}
运行结果
最后居然只有一个测试点没通过:
纠错
两种思路:
- 仍然用Dijkstra求出最短路,因为可能有多条最短路,为了记录所有可能的最短路,需要用一个vector数组保存某个结点的所有前驱节点。最后再用深度优先搜索确定发车数最少的路径。
- 直接用深度优先搜索,当发现一条更短的路径时,用该路径替换已经记录的路径,并记录minSend和minBack;当发现一条同样短的路径时,直接比较send和minSend、back和minBack,如果send比minSend更小,或send和minSend相等且back比minBack更小,则更新最短路为当前路径。
对于某条路,求send和back的方法:
遍历整条路径,计算当前总计车数currentBikeSum,当前经过站数n,则:
send = max(send, n * (capacity / 2) - currentBikeSum) //send初始值为0
遍历到达终点时直接求出收回车数:
back = max(0, send + currentBikeSum - n * (cpacity / 2))
思路一实现
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int inf = 0x7fffffff;
int capacity, stationNum, destination, roadNum;
int map[501][501];
int station[501] = { 0 };
vector<int> pre[501]; //前驱站
vector<int> nextStation[501]; //指示最短路的下一个站
bool visited[501];
int dist[501]; //到达某站的距离
int minSend = inf, minBack = inf;
vector<int> bestRoute;
void dfs(int cur, int send, int bikeSum, int stationCount, vector<int> curRoute) {
curRoute.push_back(cur);
bikeSum += station[cur];
send = max(send, stationCount * capacity / 2 - bikeSum);
if (cur == destination) {
int back = max(0, send + bikeSum - stationCount * capacity / 2); //注意加上发出的车数
if (send < minSend || send == minSend && back < minBack) {
bestRoute = curRoute;
minSend = send;
minBack = back;
}
return;
}
for (int it : nextStation[cur]) {
dfs(it, send, bikeSum, stationCount + 1, curRoute);
}
}
int main() {
fill(map[0], map[0] + 501 * 501, inf);
fill(dist, dist + 501, inf);
dist[0] = 0;
scanf("%d %d %d %d", &capacity, &stationNum, &destination, &roadNum);
for (int i = 1; i <= stationNum; i++)
scanf("%d", &station[i]);
for (int i = 0; i < roadNum; i++) {
int s1, s2, d;
scanf("%d %d %d", &s1, &s2, &d);
map[s1][s2] = d;
map[s2][s1] = d;
}
for (int i = 0;i <= stationNum;i++) {
int u = -1, minDist = inf;
for (int j = 0; j <= stationNum; j++) {
if (!visited[j] && dist[j] < minDist) {
u = j;
minDist = dist[j];
}
}
if (u == -1 || u == destination)
break;
visited[u] = true;
for (int v = 1; v <= stationNum; v++) {
if (!visited[v] && map[u][v] != inf) {
if (map[u][v] + dist[u] < dist[v]) {
dist[v] = map[u][v] + dist[u];
pre[v].clear();
pre[v].push_back(u);
}
else if (map[u][v] + dist[u] == dist[v]) {
pre[v].push_back(u);
}
}
}
}
for (int i = 0;i <= stationNum;i++) {
for (int e : pre[i]) {
nextStation[e].push_back(i);
}
}
vector<int> temp;
dfs(0, 0, 0, 0, temp);
printf("%d ", minSend);
for (int i = 0;i < bestRoute.size();i++) {
if (i != 0)
printf("->");
printf("%d", bestRoute[i]);
}
printf(" %d", minBack);
}
思路二实现
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int inf = 0x7fffffff;
int capacity, stationNum, destination, pathNum;
int bikeNum[501];
int map[501][501];
int visited[501] = { false };
vector<int> minPath;
int minSend = inf, minBack = inf;
int minTime = inf;
void dfs(int pos, int curTime, vector<int> curPath) {
if (curTime > minTime) //剪枝
return;
if (pos == destination) {
int send = 0, back = 0, bikeSum = 0;
for (int i = 1;i < curPath.size();i++) {
bikeSum += bikeNum[curPath[i]];
send = max(send, (capacity / 2) * i - bikeSum);
}
back = send + bikeSum - (curPath.size() - 1) * (capacity / 2);
back = back < 0 ? 0 : back;
if (minTime > curTime || minPath.empty() || send < minSend || send == minSend && back < minBack) {
minPath.clear();
for (int i = 0;i < curPath.size();i++) {
minPath.push_back(curPath[i]);
}
minSend = send;
minBack = back;
minTime = curTime;
}
}
for (int i = 1;i <= stationNum;i++) {
if (map[pos][i] == inf || visited[i])
continue;
visited[i] = true;
curPath.push_back(i);
dfs(i, curTime + map[pos][i], curPath);
curPath.pop_back();
visited[i] = false;
}
}
int main() {
scanf("%d %d %d %d", &capacity, &stationNum, &destination, &pathNum);
for (int i = 1;i <= stationNum;i++) {
scanf("%d", &bikeNum[i]);
}
fill(map[0], map[0] + 501 * 501, inf);
for (int i = 0;i < pathNum;i++) {
int s1, s2, time;
scanf("%d %d %d", &s1, &s2, &time);
map[s1][s2] = time;
map[s2][s1] = time;
}
vector<int> curPath;
curPath.push_back(0);
dfs(0, 0, curPath);
printf("%d ", minSend);
for (int i = 0;i < minPath.size();i++) {
if (i != 0) {
printf("->");
}
printf("%d", minPath[i]);
}
printf("% d", minBack);
}