问题描述
某市有 𝑛 个路口, 有 𝑚 段道路连接这些路口, 组成了该市的公路系统。其 中一段道路两端一定连接两个不同的路口。道路中间不会穿过路口。
由于各种原因, 在一部分道路的中间设置了一些限高杆, 有限高杆的路段 货车无法通过。
在该市有两个重要的市场 𝐴 和 𝐵, 分别在路口 1 和 𝑛 附近, 货车从市场 𝐴 出发, 首先走到路口 1 , 然后经过公路系统走到路口 𝑛, 才能到达市场 𝐵 。两 个市场非常繁华, 每天有很多货车往返于两个市场之间。
市长发现, 由于限高杆很多, 导致货车可能需要绕行才能往返于市场之间, 这使得货车在公路系统中的行驶路程变长, 增加了对公路系统的损耗, 增加了 能源的消耗, 同时还增加了环境污染。
市长决定要将两段道路中的限高杆拆除, 使得市场 𝐴 和市场 𝐵 之间的路程变短。请问最多能减少多长的距离?
输入格式
输入的第一行包含两个整数 𝑛,𝑚 分别表示路口的数量和道路的段数。
接下来 𝑚m 行, 每行四个整数 𝑎,𝑏,𝑐,𝑑 表示路口 𝑎a 和路口 𝑏 之间有一段长 度为 𝑐 的道路。如果 𝑑 为 0 , 表示这段道路上没有限高杆; 如果 𝑑 为 1 , 表示 这段道路上有限高杆。两个路口之间可能有多段道路。
输入数据保证在不拆除限高杆的情况下, 货车能通过公路系统从路口 1 正常行驶到路口 𝑛 。
输出格式
输出一行,包含一个整数, 表示拆除两段逅路的限高杆后, 市场 𝐴 和市场 𝐵 之间的路程最大减少多长距离。
样例输入
5 7
1 2 1 0
2 3 2 1
1 3 9 0
5 3 8 0
4 3 5 1
4 3 9 0
4 5 4 0
样例输出
6
样例说明
只有两段道路有限高杆, 全部拆除后, 1 到 𝑛 的路程由原来的 17 变为了 11,减少了 6。
评测用例规模与约定
对于 30% 的评测样例, 2 ≤ 𝑛 ≤ 10, 1 ≤ 𝑚 ≤ 20, 1 ≤ 𝑐 ≤ 100 。
对于 50% 的评测样例, 2 ≤ 𝑛 ≤ 100,1 ≤ 𝑚 ≤ 1000,1 ≤ 𝑐 ≤ 1000。
对于 70% 的评测样例, 2 ≤ 𝑛 ≤ 1000, 1 ≤ 𝑚 ≤ 10000, 1 ≤ 𝑐 ≤ 10000。
对于所有评测样例, 2 ≤ 𝑛 ≤ 10000, 2 ≤ 𝑚 ≤ 100000, 1 ≤ 𝑐 ≤ 10000。
解题思路
这道题明确的说,我们考虑的是固定的起点和固定的终点,那么很容易想到单源最短路径dijkstra算法,这是一个bfs+优先队列的解题方法,若读者尚未了解建议先行学习。
这道题不一样的点在于,对于边是有不同的种类的,一种是没有限高架,一种是有限高架,市长想要撤销最多两个限高架,使得固定起点和终点的路径长度得到最大优化。
这里我们使用多层图+dijkstra算法来完成,建立一个三层图,单层内点与点之间的边就是没有限高架的边,而跨层的情况我们使用第二类有限高架的单向跨层边,根据这样的设计,从第一层的起点,到第三层的终点就是经过最多两个限高架的最短距离。
对于跨层边,跨层的单向边建立起来是两条,读者可能会感到困惑。举个例子,对于Ai表示第i层的点A,则有a1可以到b2(b2不可以到a1),b1可以到a2(a2不可以到b1)。所以应当明白,如果是双向边,则双向跨层边应当会有四条,而这里的两条则是单向跨层边。
基于上述设计,我们从第一层的起点到第三层的终点,就不会存在中途回层的问题。
最后,我们还要对不同层的同一个节点,即a1->a2、a2->a3、a1->a3的边权值置为0,因为有限高架的路径也可能非常不划算,这样设计就不必担心跨层必须要经过限高架的问题了,不经过限高架路段也可以免费跨层来到下层的结点。
import java.util.*;
public class Main{
static final long INF = (long)1e9;
static int n, m;
// 这里用HashMap代替了ArrayList<Node>,可以在log复杂度处理重边
static ArrayList<HashMap<Integer, Long>> edges;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
sc.nextLine();
edges = new ArrayList<>();
for (int i = 0; i <= 3 * n; i++) {
edges.add(new HashMap<>());
}
for (int i = 1; i <= m; i++) {
String[] temp = sc.nextLine().split(" ");
int u = Integer.parseInt(temp[0]);
int v = Integer.parseInt(temp[1]);
long w = Long.parseLong(temp[2]);
int f = Integer.parseInt(temp[3]);
if (f == 1) {
link(u, v + n, w);
link(v, u + n, w);
link(u + n, v + 2 * n, w);
link(v + n, u + 2 * n, w);
} else {
link(u, v, w);
link(v, u, w);
link(u + n, v + n, w);
link(v + n, u + n, w);
link(u + 2 * n, v + 2 * n, w);
link(v + 2 * n, u + 2 * n, w);
}
}
// 同一个节点免费跨层
for (int i = 1; i <= n; i++) {
link(i, i + n, 0);
link(i + n, i + 2 * n, 0);
link(i, i + 2 * n, 0);
}
long ans = dij();
System.out.print(ans);
}
// 添加边,并处理重边,只保留最短的
static void link(int u, int v, long w) {
edges.get(u).put(v, Math.min(w, edges.get(u).getOrDefault(v, INF)));
}
static boolean[] visited;
static long[] dist;
static PriorityQueue<Node> qu;
static long dij() {
visited = new boolean[3 * n + 1];
qu = new PriorityQueue<>(Comparator.comparing(node -> node.w));
dist = new long[3 * n + 1];
Arrays.fill(dist, INF);
dist[1] = 0;
qu.offer(new Node(1, 0));
while (!qu.isEmpty()) {
Node node = qu.poll();
int u = node.id;
long s = node.w;
if (visited[u]) {
continue;
}
visited[u] = true;
for (java.util.Map.Entry<Integer, Long> entry : edges.get(u).entrySet()) {
int v = entry.getKey();
long w = entry.getValue();
if (!visited[v]) {
if (dist[v] > dist[node.id] + w) {
dist[v] = dist[node.id] + w;
qu.offer(new Node(v, dist[v]));
}
}
}
}
System.out.println(Arrays.toString(dist));
// dist[n]表示只在第一层走到终点,也就是不经过任何限高架路段的原始最短距离
return dist[n] - dist[3 * n];
}
}
// 对于优先队列中的每一个节点
// 存储的是节点的编号,以及其到起点的距离
class Node {
int id;
long w;
public Node(int id, long w) {
super();
this.id = id;
this.w = w;
}
}