題目:
假设有个国家有 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;
}
}
}