ALGO-15 旅行家的预算
ALGO-15 旅行家的预算
题目
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
一个旅行家想驾驶汽车以最少的费用从一个城市到另一个城市(假设出发时油箱是空的)。给定两个城市之间的距离 D1、汽车油箱的容量 C(以升为单位)、每升汽油能行驶的距离 D2、出发点每升汽油价格 P 和沿途油站数 N(N 可以为零),油站 i 离出发点的距离 Di、每升汽油价格 Pi(i=1,2,……N)。计算结果四舍五入至小数点后两位。如果无法到达目的地,则输出“No Solution”。
输入格式
第一行为 4 个实数 D1、C、D2、P 与一个非负整数 N;
接下来 N 行,每行两个实数 Di、Pi。
输出格式
如果可以到达目的地,输出一个实数(四舍五入至小数点后两位),表示最小费用;否则输出“No Solution”(不含引号)。
样例输入
\(275.6\ \ \ 11.9\ \ \ 27.4\ \ \ 2.8\ \ \ 2\)
\(102.0\ \ \ 2.9\)
\(220.0\ \ \ 2.2\)
样例输出
26.95
题解
思路
思路1
对应第一个代码答案
- 先判断是否能走通,即两个站点之间满油车能不能通过
- 找到下一个站点(再满油的范围)
- 判断是否有更便宜的站点
- 在现有的油量(不加油)可以到达的范围:直接设为下一个站点
- 需要加油才能达到:只有在上面的条件为否的情况才设为下一个站点
- 没有更便宜的
- 看能不能直接到终点,能去就直接去
- 从贵的的里找一个最便宜的
- 判断是否有更便宜的站点
- 计算当前需要的加油量(费用)
- next是终点,加适量
- 下一站便宜,加适量
- 下一站贵,加满
- 循环 2-3 直到终点
思路2
找在最大行驶距离内寻找比当前便宜的加油站,然后判断是否能一次到达,不能的话先加满,然后一个一个判断直到剩下的油量不足到下一个加油站就加油,加适量。
于是有以下 2 situations:
- 这个站点 j 就是 pos
- 从 pos 就可以走到终点于是我们把油加到刚好到达终点即可
cost += ((d[i] - d[pos]) / d2 - remain)*p[pos];就得到了最后答案。
remain 是当前剩余的油 - 从 pos 不能一把走到终点
于是,从当前位置走,走到哪里加油都不够在 pos 这里加油划算。所以加满。
- 从 pos 就可以走到终点于是我们把油加到刚好到达终点即可
- 这个站点 j 是在 pos 后面的某个站点
- 当前剩余的油 remain 不够走到 j。于是我们,把油加来刚好能够走到 j 就行了。(因为 j 这里好好便宜!)
- 剩余的油 remain 足够,直接开过去就行了。
代码
我自己写的,感觉更有面向对象的风格
import java.util.Scanner;
public class ALGO_15 {
static double[] d, p; // D 是油站距离,P 是油站价格
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double D1 = sc.nextDouble();
double C = sc.nextDouble();
double D2 = sc.nextDouble();
double P = sc.nextDouble();
int N = sc.nextInt();
d = new double[N + 2];
p = new double[N + 2];
for (int i = 1; i < N + 1; i++) {
d[i] = sc.nextDouble();
p[i] = sc.nextDouble();
}
// 起始站
d[0] = 0;
p[0] = P;
// 终点站
d[d.length - 1] = D1;
p[p.length - 1] = 0;
sc.close();
double full_run = D2 * C;
if (reachable(full_run)) {
System.out.printf("%.2f", getMinCost(C, D2));
} else {
System.out.println("No Solution");
}
}
/**
* 计算出最小费用
* @param {double} full_run 可行驶最远距离
* @return {double} 最小费用
*/
private static double getMinCost(double capacity, double distance) {
final double full_run = distance * capacity;
double min_cost = 0;
double remain = 0; // 剩余油量
int pos = 0; // 当前位置
double remain_run;
while (pos < d.length - 1) {
remain_run = distance * remain;
int index = next(pos, full_run, remain_run);
if (index == d.length - 1) {
min_cost += (d[index] - d[pos] - remain_run) / distance * p[pos];
} else if (p[index] > p[pos]) {
min_cost += p[pos] * (capacity - remain);
remain = capacity - (d[index] - d[pos]) / distance;
} else {
if (d[index] - d[pos] > remain_run) {
min_cost += (d[index] - d[pos] - remain_run) / distance * p[pos];
remain = 0;
} else {
remain -= (d[index] - d[pos]) / distance;
}
}
pos = index;
}
return min_cost;
}
/**
* 找到从 pos+1 到可行使距离的最优站点
* @param {int} pos 当前位置
* @param {double} full_run 可行驶最远距离
* @param {double} remain_run 剩余可行驶距离
* @return {int} 最优站点 (pos 表示没有找到)
*/
private static int next(int pos, double full_run, double remain_run) {
int index = pos;
for (int i = pos + 1; i < d.length; i++) {
if (d[i] - d[pos] > full_run) {// 超过可行使距离
break;
}
if (p[i] <= p[index]) {// 找到更便宜的站点
if (remain_run >= d[i] - d[pos]) {// 剩余油量足够
index = i;
} else {
if (index == pos) {
index = i;
}
}
}
}
// 没有找到更便宜的站点
if (index == pos) {
if (full_run >= d[d.length - 1] - d[pos]) {// 直接到终点
index = d.length - 1;
} else {
// 就在大的之中找一个最小的
double price = Integer.MAX_VALUE;
for (int i = pos + 1; i < d.length; i++) {
if (d[i] - d[pos] > full_run) {// 超过可行使距离
break;
}
if (p[i] < price) {
index = i;
price = p[i];
}
}
}
}
return index;
}
/**
* 判断是否可以到达终点
* @param {double} full_run 可行驶最远距离
* @return {boolean}
*/
private static boolean reachable(double full_run) {
for (int i = 1; i < d.length; i++) {
if (d[i] - d[i - 1] > full_run) {
return false;
}
}
return true;
}
}
网上的答案,略作修改
import java.util.Scanner;
public class ALGO_15 {
static double a[]; //位置
static double b[];//油价
static double last_oil; //油箱剩余空间
static double run_c; //剩余油料可以行走的最大距离
static int best_pos; //形成内最小油料价格的位置
static double full_run; //邮箱在满了的情况下可以跑多远
static double use = 0;
static double d1, c, d2, p;
static int n;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
d1 = sc.nextDouble();
c = sc.nextDouble();
d2 = sc.nextDouble();
p = sc.nextDouble();
n = sc.nextInt();
a = new double[n + 2];
b = new double[n + 2];
for (int i = 1; i <= n; i++) {
a[i] = sc.nextDouble();
b[i] = sc.nextDouble();
}
sc.close();
a[0] = 0;
a[n + 1] = d1;
b[0] = p;
b[n + 1] = 0;
last_oil = 0; //初始化邮箱剩余油量
best_pos = 0; //初始化初始位置
full_run = c * d2;
boolean passed = true;
for (int i = 0; i <= n; i++) { //验证是否可达
if (a[i + 1] - a[i] > full_run) {
System.out.println("No Solution");
passed = false;
break;
}
}
if (passed) {
run();
System.out.println(String.format("%.2f", use));
}
}
public static void run() {
if (best_pos == n + 1)
return;
int flag = 0; //标记在最大行程内有没有找到费用更少的收费站
run_c = last_oil * d2;
for (int i = best_pos + 1; i <= n + 1; i++) {
if (a[i] - a[best_pos] <= full_run) { //必须在full_run范围之内
if (b[i] <= b[best_pos]) { //如果是更优的选择
flag = 1; //标记
if (run_c >= a[i] - a[best_pos]) { //如果剩下的油已经够跑了
last_oil = (a[i] - a[best_pos]) / d2; //剩余油量更新
} else { //如果剩下的油不够跑
last_oil = 0; //要恰好跑到价格更低的加油站
use += (a[i] - a[best_pos] - run_c) / d2 * b[best_pos];
}
best_pos = i; //更新最优节点
break;
}
}
}
if (flag == 0) { //没有匹配到最优解
int better_pos = best_pos + 1;
for (int i = best_pos + 1; i <= n + 1; i++) { //一定是不可能到n+1的,因为n+1的在上面是一定可以匹配的!因为a[n+1].price是0!
if (a[i] - a[best_pos] <= full_run) {
if (b[i] < b[better_pos]) {
better_pos = i;
}
}
}
//找到了更优值
use += (c - last_oil) * b[best_pos]; //一定是加满油最好了!因为在满油的行车范围内都到不了最优的,所以一定要加满油
last_oil = c - (a[better_pos] - a[best_pos]) / d2; //先更新到达better_pos位置剩下的油料
best_pos = better_pos;
}
run();
}
}