题目
输入示例一
1 | 10 15 |
输出示例一
1 | Time = 6: 5 => 4 => 8 => 3 |
输入示例二
1 | 7 9 |
输出示例二
1 | Time = 3; Distance = 4: 3 => 2 => 5 |
Dijstra算法
- bfs+优先队列其实就是dijstra算法
- dijstra维护result set,源节点到这个set里的点的最短路径已经找到,所以result set只会有node被加进去而不会有node被踢出去
- 不同于算法导论描述的dijstra算法——算导里的堆是点的堆,所以当发现有新的路径可以以更小的代价到达某点时(该点之前已经在堆里),会使用堆decrease key操作——把这个点的权值减低,并且调整堆使得堆合法。但是stl的优先队列并没有decrease key的操作,所以使用另一种实现——允许某点多次加入到优先队列里——其实就等价于优先队列里放Edge。所以在从优先队列里pop出最小权的点时,要判断该点是否已经处于result set里。这样做会使得复杂度从$O((E+V)lgV)$ 变为$O(ElgE)$
Dijstra找出所有最短路径
- 从src到dest的路径上的某些节点有多种到达方法——这就导致了多条路径。所以我们要维护所有节点的parent list——无论从哪个parent 到这个节点都是最短的。
- 具体实现是:从优先队列其pop出某节点,该节点已经处于result set里,但是源节点到该节点的代价与result中的这个节点的路径代价相同,那么这条新的路径就是另一个条到达这个节点的路径。使用一个vector来存储某个节点的parent list——比如
vector<int> parent[600]
,则parent[n]
代表的vector就是编号为n的节点的parent list - 然后使用dfs去计算所有可能的路径的另外一种代价——比如此题,以时间为权重的最短路径有多条,但是我们还需要计算出这些路径在使用路径长度作为权重时该路径的代价。这一步可以使用记忆化搜索。
需要注意的点
不可以这样寻找同代价的不同parent 节点
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 参数me是该edge的终点,p是该edge的起点,t出发的源节点到这条edge的终点的代价
// 其实edge t就代表了节点me
struct Edge_t {
int me;
int p;
int t;
Edge_t(int m, int h, int y) : me(m), p(h), t(y) {}
bool operator<(const Edge_t &x) const { return x.t < this->t; }
};
while (!que.empty()) {
Edge_t t = que.top();
que.pop();
while (!que.empty() && que.top().t == t.t && que.top().me == t.me)
...
}可能某
Edge h
确实跟pop出来的Edge t
是代表的是同一个节点——也就是h.me==t.me
,并且代价相同。但是在堆中,还有同等代价的Edge p
,其终点不是t.me
——也就是p.me!=t.me
,然后该Edge p
在堆中的位置处在Edge h
前面,所以上面那个while就没办法获得Edge h
从而无法获取完整的parent list。另一种情况更为严重——如果某边是0权重,那么就可能出现“把t.me加到result set之后,后面的循环才产生出另一条与src到t.me等代价的路径”——所以当pop出
Edge t
后立刻寻找同等代价的路径是不行的,因为该边其实还没有进入到堆里
考虑自循环边
- 直接判断要求某点A邻接的点不与A相同即可实现
因为有0权重的边,所以可能有0权重环路,所以后面的dfs要注意
- dfs递归访问某条路径时要维护已经访问到的节点的列表,确保不重复访问该条路径上已经出现过的点,做法是维护一个visit数组,有点类似回溯法
INF的取值要注意
- dfs过程中有可能访问的那条路径无法从src到dest,从而递归到最深的那次dfs的调用会返回INF,如果INF是0xFFFFFFF,加上后面的权重,就会溢出,从而使得
INF+weight[i]
不再是INF
自己生成数据对拍时
- 要注意不要生成多重边,网上很多ac的代码都是不支持多重边的
代码
1 |
|