蓝桥杯第2357题——限高杆(多层图+dijkstra)

问题描述

某市有 𝑛 个路口, 有 𝑚 段道路连接这些路口, 组成了该市的公路系统。其 中一段道路两端一定连接两个不同的路口。道路中间不会穿过路口。

由于各种原因, 在一部分道路的中间设置了一些限高杆, 有限高杆的路段 货车无法通过。

在该市有两个重要的市场 𝐴 和 𝐵, 分别在路口 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;
	}
}

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值