P4009 汽车加油行驶问题 - 分层图最短路

新博客地址:https://gyrojeff.top,欢迎访问! 本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客👇

本文标题:P4009 汽车加油行驶问题 - 分层图最短路

文章作者:gyro永不抽风

发布时间:2020年09月18日 - 22:09

最后更新:2020年09月19日 - 19:09

原始链接:http://hexo.gyrojeff.moe/2020/09/18/P4009-%E6%B1%BD%E8%BD%A6%E5%8A%A0%E6%B2%B9%E8%A1%8C%E9%A9%B6%E9%97%AE%E9%A2%98-%E5%88%86%E5%B1%82%E5%9B%BE%E6%9C%80%E7%9F%AD%E8%B7%AF/

许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 转载请保留原文链接及作者!

题目

题目描述

给定一个 N×NN \times N 的方形网格,设其左上角为起点◎,坐标(1,1)(1,1)XX 轴向右为正, YY 轴向下为正,每个方格边长为 11 ,如图所示。

一辆汽车从起点◎出发驶向右下角终点▲,其坐标为 (N,N)(N,N)

在若干个网格交叉点处,设置了油库,可供汽车在行驶途中加油。汽车在行驶过程中应遵守如下规则:

  1. 汽车只能沿网格边行驶,装满油后能行驶 KK 条网格边。出发时汽车已装满油,在起点与终点处不设油库。

  2. 汽车经过一条网格边时,若其 XX 坐标或 YY 坐标减小,则应付费用 BB ,否则免付费用。

  3. 汽车在行驶过程中遇油库则应加满油并付加油费用 AA

  4. 在需要时可在网格点处增设油库,并付增设油库费用 CC(不含加油费用AA )。

  5. N,K,A,B,CN,K,A,B,C 均为正整数, 且满足约束: 2N100,2K102\leq N\leq 100,2 \leq K \leq 10

设计一个算法,求出汽车从起点出发到达终点所付的最小费用。

输入格式

文件的第一行是 N,K,A,B,CN,K,A,B,C 的值。

第二行起是一个N×NN\times N010-1 方阵,每行 NN 个值,至 N+1N+1 行结束。

方阵的第 ii 行第 jj 列处的值为 11 表示在网格交叉点 (i,j)(i,j) 处设置了一个油库,为 00 时表示未设油库。各行相邻两个数以空格分隔。

输出格式

程序运行结束时,输出最小费用。

输入输出样例

输入 #1
9 3 2 3 6
0 0 0 0 1 0 0 0 0
0 0 0 1 0 1 1 0 0
1 0 1 0 0 0 0 1 0
0 0 0 0 0 1 0 0 1
1 0 0 1 0 0 1 0 0
0 1 0 0 0 0 0 1 0
0 0 0 0 1 0 0 0 1
1 0 0 1 0 0 0 1 0
0 1 0 0 0 0 0 0 0
输出 #1
12

说明/提示

2n100,2k102 \leq n \leq 100,2 \leq k \leq 10

题解

这道题一开始分层图的建立着实没有想出来,甚至看了题解都没想出来(我,菜)。所以特此记录。其实是这样的,因为一辆车总共只有$k$格油,所以我们可以建立$k + 1$层图,第$i$层图代表已经用掉了多少油。

接下来,我们考虑这个图如何构建:

  • 我们用三元组$(i, j, l)$来表示点,意为:第$l$层中的$(i, j)$
  • 因为题目中说不消耗钱的话只能向右或者向下,所以:

    $$(i, j, l) \rightarrow (i, j + 1, l + 1), w = 0 $$

    $$(i, j, l) \rightarrow (i + 1, j + 1, l), w = 0 $$

  • 因为题目中说向左或者向上要花钱,所以:

    $$(i, j, l) \rightarrow (i - 1, j, l + 1), w = b $$

    $$(i, j, l) \rightarrow (i, j - 1, l + 1), w = b $$

  • 在加油站的时候,情况和上面的就不太一样了,加油站的点应该都要指向该点的第$0$层(因为油必须加满),而且费用为$A$:

    $$(i, j, l) \rightarrow (i, j, 0), w = A $$

  • 还有就是不在加油站时可以随意添加油站,就是要花钱罢了:

    $$(i, j, l) \rightarrow (i, j, 0), w = A + C $$

在图建立完之后,我们就可以发现:求最小费用其实就是求最短路,然后最后无论剩多少油都是可能的,所以我们要把$(n, n, l)$都遍历一下取一个最小。

还有一点:要注意不要数组越界,这就是为什么代码里有好多if

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <queue>
#include <cstdio>

using namespace std;

const int MAXN = 105;
const int MAXK = 15;

int n, k, a, b, c;
int head[MAXN * MAXN * MAXK], dis[MAXN * MAXN * MAXK], ecnt = 2;
bool vis[MAXN * MAXN * MAXK];
struct edge { int to, next, w; } g[2 * 7 * MAXK * MAXN * MAXN];
void add_edge(int u, int v, int w) { g[ecnt] = (edge) { v, head[u], w }; head[u] = ecnt ++; }
void add_edge(int x1, int y1, int l1, int x2, int y2, int l2, int w) {
add_edge((y1 - 1) * n + x1 + l1 * n * n, (y2 - 1) * n + x2 + l2 * n * n, w);
}

struct node {
int dis, pos;
friend bool operator < (const node &a, const node &b) { return a.dis > b.dis; }
};
priority_queue<node> q;

void dijkstra() {
for (int i = 1; i <= n * n * (k + 1); i ++) dis[i] = ((1 << 30) - 1) * 2;
dis[1] = 0;
q.push((node) { dis[1], 1 });
while (!q.empty()) {
int u = q.top().pos; q.pop();
if (vis[u]) continue;
vis[u] = true;
for (int e = head[u]; e; e = g[e].next) {
int v = g[e].to;
if (dis[v] > dis[u] + g[e].w) {
dis[v] = dis[u] + g[e].w;
q.push((node) { dis[v], v });
}
}
}
}

int main() {
cin >> n >> k >> a >> b >> c;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n; j ++) {
int oil = 0; cin >> oil;
if (oil) {
for (int l = 1; l <= k; l ++)
add_edge(j, i, l, j, i, 0, a);
if (i < n) add_edge(j, i, 0, j, i + 1, 1, 0);
if (j < n) add_edge(j, i, 0, j + 1, i, 1, 0);
if (i > 1) add_edge(j, i, 0, j, i - 1, 1, b);
if (j > 1) add_edge(j, i, 0, j - 1, i, 1, b);
} else {
for (int l = 0; l < k; l ++) {
if (i < n) add_edge(j, i, l, j, i + 1, l + 1, 0);
if (j < n) add_edge(j, i, l, j + 1, i, l + 1, 0);
if (i > 1) add_edge(j, i, l, j, i - 1, l + 1, b);
if (j > 1) add_edge(j, i, l, j - 1, i, l + 1, b);
}
for (int l = 1; l <= k; l ++)
add_edge(j, i, l, j, i, 0, c + a);
}
}
}
dijkstra();
int ans = ((1 << 30) - 1) * 2;
for (int l = 0; l <= k; l ++) ans = min(ans, dis[n * n * (1 + l)]);
cout << ans << endl;
return 0;
}
posted @ 2020-09-19 00:11  gyro永不抽风  阅读(98)  评论(0编辑  收藏  举报