【JAVA】求有两次调整权值机会的最短路径(Pro20210813)(BFS(Dijkstra思想))

【JAVA】求有两次调整权值机会的最短路径(Pro20210813)(BFS(Dijkstra思想))

題目:

假设有个国家有 N 座城市,通过 M 条道路连通,城市从 1 到 N 进行编号,每条道路上都会收取过路费。
据说,这个国家每年的暑期会向全体市民发放两张过路费折扣券(每张券的折扣力度可能会不同)。John 住在 1 号城市,准备前往 N 号城市度假,想要使用折扣券确保支付最少的过路费。
在这里插入图片描述

如上 [图] 所示,假设从 1 号城市出发,前往 8 号城市,收到的折扣券金额分别是 2 和 10。
在路径 1 → 2 → 4 → 5 → 6 → 8、路径 1 → 2 → 4 → 6 → 8、以及路径 1 → 5 → 8 中,如果选择路径 1 → 2 → 4 → 5 → 6 → 8,不使用折扣券的情况总费用是 17,但如果使用折扣券,到 8 号城市的总费用是 9。(从 6 号城市到 8 号城市时,使用金额为 10 的折扣券,在剩余其中一条道路上使用金额为 2 的折扣券。)
如果选择路径 1 → 2 → 4 → 6 → 8,不使用折扣券的情况总费用是 19,但如果使用折扣券,到 8 号城市的总费用是 9。(从 4 号城市到 6 号城市时,使用金额为 10 的折扣券,在剩余其中一条道路上使用金额为 2 的折扣券。)
如果选择路径 1 → 5 → 8,不使用折扣券的情况需要花费 18,如果使用折扣券,到 8 号城市的总费用就是 6,这也是能实现的最小总费用。(从 5 号城市到 8 号城市时,使用金额为 10 的折扣券,从 1 城市到 5 号城市时,使用金额为 2 的折扣券。)
请帮助 John 计算出在暑期使用折扣券从 1 号城市前往 N 号城市度假要支付的最少过路费。

[限制条件]
1.城市数量 N 为介于 2 到 40,000 之间的整数。
2.道路数量 M 为介于 N-1 到 100,000 之间的整数。
3.道路的过路费 M 为介于 1 到 100,000 之间的整数。
4.折扣券的折扣金额 P 为介于 1 到 100,000 之间的整数。
5.每张折扣券只能使用一次,一条道路上不能同时使用两张折扣券。
6.如果使用的折扣券金额比该道路的过路费更高,路过该道路需支付的过路费为 0。
[输入]
在第一行,给定测试用例数量 T。在每个测试用例的第一行,给定城市数量 N、道路数量 M,以空格分隔。接下来的一行,给定两张折扣券的金额,以空格分隔。接下来从第三行起到后面的 M 行,每行给定一条道路的信息(道路两端的城市编号 A 和 B,以及道路的过路费 C),格式为 A B C,三者以空格分隔。
[输出]
每行输出一个测试用例。每个测试用例,都输出“#x”(其中 x 表示测试用例编号,从 1 开始),然后是使用折扣券在暑期前往目的地的过程中要支付的最少费用。

[输入和输出示例]
(输入)
2
8 13
2 10
1 2 3
1 5 7
2 4 2
3 4 3
3 6 11
3 7 15
4 5 2
4 6 8
5 6 4
5 8 11
6 7 2
6 8 6
7 8 4
6 8
3 20
1 2 3
4 5 3
5 6 3
3 1 5
2 3 3
6 1 15
3 4 3
3 6 7

(输出)
#1 6
#2 0

思路:

參考題目:【JAVA】求飞机游戏中最小横向移动次数Pro20210719(DP / BFS(Dijkstra思想))

代码:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class Main {
	static int T, N, M;
	static long CARD_A, CARD_B; // A、B折扣券
	static ArrayList<Edg>[] DATA; // 原始数据

	public static void main(String[] args) throws Exception {
		System.setIn(new FileInputStream("D:\\SW\\TestCase\\sample_input_20210813.txt"));
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;
		int T = Integer.parseInt(br.readLine());

		for (int tc = 1; tc <= T; tc++) {
			st = new StringTokenizer(br.readLine());
			N = Integer.parseInt(st.nextToken());
			M = Integer.parseInt(st.nextToken());

			DATA = new ArrayList[N + 1];
			for (int i = 1; i <= N; i++)
				DATA[i] = new ArrayList<>();

			st = new StringTokenizer(br.readLine());
			CARD_A = Long.parseLong(st.nextToken()); // A折扣券
			CARD_B = Long.parseLong(st.nextToken()); // B折扣券

			// 邻接链表读入边、权值数据并存储
			for (int i = 0; i < M; i++) {
				st = new StringTokenizer(br.readLine());

				int from = Integer.parseInt(st.nextToken());
				int to = Integer.parseInt(st.nextToken());
				long cost = Long.parseLong(st.nextToken());

				DATA[from].add(new Edg(to, 0, cost));
				DATA[to].add(new Edg(from, 0, cost));
			}

			// 迪杰斯特拉求最短路径
			long asw = dijkstra();

			System.out.printf("#%d %d\n", tc, asw);
		}
	}

	static long dijkstra() {
		// 分四种情况记录已确定最短路径的点:
		// visited[0]:不使用折扣券
		// visited[1]:使用A折扣券,不使用B折扣券
		// visited[2]:不使用A折扣券,使用B折扣券
		// visited[3]:使用A、B折扣券
		boolean visited[][] = new boolean[4][N + 1];
		for (int i = 0; i < 4; i++)
			visited[i][1] = true;

		// 分四种情况记录已确定最短路径的点:
		// cost[0]:不使用折扣券时,各点的最短路径
		// cost[1]:使用A折扣券,不使用B折扣券时,各点的最短路径
		// cost[2]:不使用A折扣券,使用B折扣券时,各点的最短路径
		// cost[3]:使用A、B折扣券时,各点的最短路径
		long cost[][] = new long[4][N + 1];
		for (int i = 0; i < 4; i++)
			Arrays.fill(cost[i], Long.MAX_VALUE); // 初始化最短路径为最大值

		// 按当前到达点的成本升序排序
		PriorityQueue<Edg> pq = new PriorityQueue<>(new Comparator<Edg>() {
			@Override
			public int compare(Edg o1, Edg o2) {
				return Long.compare(o1.cost, o2.cost);
			}
		});
		pq.add(new Edg(1, 0, 0L)); // 设定起点为1号点

		// 从当前到达点,广搜下一个可以到达的点
		while (!pq.isEmpty()) {
			Edg now = pq.poll(); // 获取当前到达的点

			if (now.house == N) // 若当前点已经到达N号点,则满足题意的最短路径已经求得
				return now.cost;

			if (cost[now.card][now.house] <= now.cost) // 若当前节点的成本 <= 之前已知的最短路径,则剪枝
				continue;

			// 松弛操作 (PS:这个松弛操作不是最优的,但足以解决当前算法)
			cost[now.card][now.house] = now.cost;
			visited[now.card][now.house] = true;

			// 遍历当前点的连接点,分情况广搜
			for (Edg next : DATA[now.house]) {
				if (visited[now.card][next.house]) // 若当前节点的最短路径已经确定,则剪枝
					continue;

				// 第一种情况:从当前点到下一个点,不使用折扣券
				pq.add(new Edg(next.house, now.card, now.cost + next.cost));

				// 第二种情况:从当前点到下一个点,使用A折扣券
				if (now.card % 2 == 0) {
					pq.add(new Edg(next.house, now.card | 1, now.cost + Math.max(next.cost - CARD_A, 0)));
				}

				// 第三种情况:从当前点到下一个点,使用B折扣券
				if (now.card / 2 == 0) {
					pq.add(new Edg(next.house, now.card | 2, now.cost + Math.max(next.cost - CARD_B, 0)));
				}
			}
		}

		return 0;
	}

	static class Edg {
		int house, card;
		long cost;

		public Edg(int h, int c, long cost) {
			this.house = h;
			this.card = c;
			this.cost = cost;
		}
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值