java 旅行家的预算_旅行家的预算 题解

题目描述

一个旅行家想驾驶汽车以最少的费用从一个城市到另一个城市(假设出发时油箱是空的)。给定两个城市之间的距离D1、汽车油箱的容量C(以升为单位)、每升汽油能行驶的距离D2、出发点每升汽油价格P和沿途油站数N(0<=N<=100),油站i离出发点的距离Di、每升汽油价格Pi(i=1,2,……N)。计算结果四舍五入至小数点后两位。如果无法到达目的地,则输出“No solution”。

输入格式 第1行:5个空格分开的数据,分别表示D1 C D2 P N 第2..N+1行:每行3个空格分开的数据,分别表示油站号i, 该油站距出发点的距离Di,该油站每升汽油的价格Pi

输出格式 第1行:一个数据,表示最少费用。

样例输入

275.6 11.9 27.4 2.8 2

1 102.0 2.9

2 220.0 2.2

样例输出

26.95

分析

这道题还是有难度的,在与一位大巨佬两个小时的语音通话后,总结出了AC这道题的方法,如下:主要是没看懂GM的题解

首先,这是一道贪心题:花最少的钱走完全程。现在,我们先来点简单的

改变一下题目,把油箱的容量改为无限,则贪心策略显而易见:

step1.

起点时一定要加油的,每升要用P元。假设中途有三个加油站,且你不想在中途加油,那么你的其实就是每一段都用的P价格的油

start--P--a[1]--P--a[2]--P--a[3]--P--end

如果我告诉你第二个加油站每升只要 P-1 元,你还会傻傻的用上面这个方案吗?我猜不会,你可以一开始就加刚好能到第二个加油站的油,后半程就可以换成 P-1 元的油了,如下图

start--P--a[1]--P--a[2]--(P - 1)--a[3]--(P - 1)--end

是不是赚了?我们把它一推广,就可以得到

step2.

在每个加油站加油只需加到能到达后面油价最低(且比它的油价还低)的加油站即可,若找不到这种加油站,恭喜你,可以准备直达了,中途不用换油,记:在第 i 个加油站加油用了 cost[i] 元,则有

int mi = INT_MAX, v = i;

for(int j = i + 1; j <= n; i++) {

if(a[j].p < mi && a[j].p < a[i].p) {

mi = a[j].p;

v = j

}

}

if(v == i) cost[i] = (d1 - a[i].d) / d2 * a[i].p;

else cost[i] = (a[v].d - a[i].d) / d2 * a[i].p;

不一定对哈,毕竟没认真实现过

前面的那么多都是引入,开始正题:

引入已经说的很清楚了,在这道题的正解中,我们只是多加入了一个判断无解:当在一个加油站加满油还不能到达下一个加油站时,旅行失败。

还有就是因为油箱容量有上限,所有我们还需要看一下当前油箱省的油最远能到哪一个加油站,确定选最小值的距离范围

我们还考虑了一下,距离远近顺序的问题,就是一号加油站可能在二号加油站后面,所以先有一次排序。但最后发现,数据中没有这一情况。

根据思路不难得出

AC代码

#include

#include

#include

using namespace std;

const int MAXN = 105;

struct data { // 结构体

int i;

double di, pi;

//油站 i 离出发点的距离 Di, 每升汽油价格 Pi

} a[MAXN];

bool cmp(data x, data y) { // sort cmp 函数

return x.di < y.di;

}

int main() {

double d1, c, d2, p;

int n;

scanf ("%lf %lf %lf %lf %d", &d1, &c, &d2, &p, &n);

//给定两个城市之间的距离 D1, 汽车油箱的容量 C, 每升汽油能行驶的距离 D2, 出发点每升汽油价格 P和沿途油站数 N

a[0].di = 0, a[0].pi = p;

for (int i = 1; i <= n; i++) {

scanf ("%d %lf %lf", &a[i].i, &a[i].di, &a[i].pi);

a[i].di /= d2;

}

a[n + 1].di = d1 / d2;

n += 2;

sort(a, a + n, cmp);

for (int i = 1; i < n; i++)

if (a[i].di - a[i - 1].di > c) { // 判断无解

printf("No solution");

return 0;

}

double rest = 0, cost;

for (int i = 0; i < n; i++) {

int min_pos = i; // 价格最低的加油站的位置

double min_val = a[i].pi; // 价格最低的加油站的价格

int j, k;

// j 剩余油量能到达的最远的加油站

// k j + 1 到 n - 1 中第一个比 minpos 便宜的点

for (j = i + 1; j < n && a[j].di - a[i].di <= rest; j++)

if (min_val < a[j].pi)

min_val = a[j].pi, min_pos = j;

//求出价格最低的加油站的相关信息

for (k = j; k < n; k++)

if (a[k].pi < min_val)

break;

if (k == n) // 判断能否到终点

k = n - 1;

rest -= (a[min_pos].di - a[i].di);

double end = (a[k].di - a[min_pos].di);

// rest 第一部分的路程

// end 第二部分的路程

if (end <= c) { // 能直接到达 K

cost += (end - rest) * a[min_pos].pi; // 加油

if (k == n - 1)

break;

else

i = k - 1, rest = 0; // 更新

}

else { // 否则加满油

cost += (c - rest) * a[min_pos].pi;

i = min_pos;

rest = c;

rest -= a[i + 1].di - a[i].di;

}

}

printf("%.2lf\n", cost);

return 0;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
06-01
这道题是一道典型的费用限制最短路题目,可以使用 Dijkstra 算法或者 SPFA 算法来解决。 具体思路如下: 1. 首先,我们需要读入输入数据。输入数据中包含了道路的数量、起点和终点,以及每条道路的起点、终点、长度和限制费用。 2. 接着,我们需要使用邻接表或邻接矩阵来存储图的信息。对于每条道路,我们可以将其起点和终点作为一个有向边的起点和终点,长度作为边权,限制费用作为边权的上界。 3. 然后,我们可以使用 Dijkstra 算法或 SPFA 算法求解从起点到终点的最短路径。在这个过程中,我们需要记录到每个点的最小费用和最小长度,以及更新每条边的最小费用和最小长度。 4. 最后,我们输出从起点到终点的最短路径长度即可。 需要注意的是,在使用 Dijkstra 算法或 SPFA 算法时,需要对每个点的最小费用和最小长度进行松弛操作。具体来说,当我们从一个点 u 经过一条边 (u,v) 到达另一个点 v 时,如果新的费用和长度比原来的小,则需要更新到达 v 的最小费用和最小长度,并将 v 加入到优先队列(Dijkstra 算法)或队列(SPFA 算法)中。 此外,还需要注意处理边权为 0 或负数的情况,以及处理无法到达终点的情况。 代码实现可以参考以下样例代码: ```c++ #include <cstdio> #include <cstring> #include <queue> #include <vector> using namespace std; const int MAXN = 1005, MAXM = 20005, INF = 0x3f3f3f3f; int n, m, s, t, cnt; int head[MAXN], dis[MAXN], vis[MAXN]; struct Edge { int v, w, c, nxt; } e[MAXM]; void addEdge(int u, int v, int w, int c) { e[++cnt].v = v, e[cnt].w = w, e[cnt].c = c, e[cnt].nxt = head[u], head[u] = cnt; } void dijkstra() { priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q; memset(dis, 0x3f, sizeof(dis)); memset(vis, 0, sizeof(vis)); dis[s] = 0; q.push(make_pair(0, s)); while (!q.empty()) { int u = q.top().second; q.pop(); if (vis[u]) continue; vis[u] = 1; for (int i = head[u]; i != -1; i = e[i].nxt) { int v = e[i].v, w = e[i].w, c = e[i].c; if (dis[u] + w < dis[v] && c >= dis[u] + w) { dis[v] = dis[u] + w; q.push(make_pair(dis[v], v)); } } } } int main() { memset(head, -1, sizeof(head)); scanf("%d %d %d %d", &n, &m, &s, &t); for (int i = 1; i <= m; i++) { int u, v, w, c; scanf("%d %d %d %d", &u, &v, &w, &c); addEdge(u, v, w, c); addEdge(v, u, w, c); } dijkstra(); if (dis[t] == INF) printf("-1\n"); else printf("%d\n", dis[t]); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值