題目
With highways available, driving a car from Hangzhou to any other city is easy. But since the tank capacity of a car is limited, we have to find gas stations on the way from time to time. Different gas station may give different price. You are asked to carefully design the cheapest route to go.
Input Specification:
Each input file contains one test case. For each case, the first line contains 4 positive numbers: Cmax (≤ 100), the maximum capacity of the tank; D (≤30000), the distance between Hangzhou and the destination city; Davg (≤20), the average distance per unit gas that the car can run; and N (≤ 500), the total number of gas stations. Then N lines follow, each contains a pair of non-negative numbers: Pi , the unit gas price, and Di (≤D), the distance between this station and Hangzhou, for i=1,⋯,N. All the numbers in a line are separated by a space.
Output Specification:
For each test case, print the cheapest price in a line, accurate up to 2 decimal places. It is assumed that the tank is empty at the beginning. If it is impossible to reach the destination, print The maximum travel distance = X where X is the maximum possible distance the car can run, accurate up to 2 decimal places.
Sample Input 1:
50 1300 12 8
6.00 1250
7.00 600
7.00 150
7.10 0
7.20 200
7.50 400
7.30 1000
6.85 300
Sample Output 1:
749.17
Sample Input 2:
50 1300 12 2
7.10 0
7.00 600
Sample Output 2:
The maximum travel distance = 1200.00
題目解讀
題目大意:汽車從杭州出發可以通過高速公路去任何城市,但是油箱的容量是有限的,路上有很多加油站,每個加油站的價格不同,為汽車設計一個從杭州到終點的最便宜的加油策略。
輸入:第一行:Cmax
表示油箱最大容量,D
表示杭州到目的地的距離,Davg
表示平均每單位的汽油可以讓汽車行駛的距離,N
表示途中加油站的數量;接下來 N
行:給出給個加油站的單位油價Pi
和杭州(起點)到這個站點的距離Di
。
輸出:求汽車從杭州到終點的最少花費(精確到兩位小數)。如果不能夠到達,就輸出汽車能夠行駛的最大距離(精確到兩位小數)。
思路分析
核心思想:貪心演算法(每次尋找區域性最優,在最便宜的加油站加最多的油)。
前期準備:
- 最終輸出無論是距離還是價格都要求精確到兩位小數,雖然從給出的輸入資料來看,距離、郵箱容量等好像都是整數,但為了操作方便,避免運算過程精度丟失,我們全都用
double
儲存。(再說了,它給出的資料說不定就是坑你的呢?) - 設定
結構體陣列
儲存每個加油站的單價和到杭州的距離。 - 按照到杭州的距離對結構體陣列排序,因為輸入是無序的。
- 排序完判斷第1個結構體到杭州的距離是否為0,也就是說最近的加油站是不是在起點。因為題目說了假定剛開始郵箱沒有油,那麼如果起點處沒有加油站,就比欸想開車了,直接輸出並返回吧。
貪心核心:怎麼實現每次都做出區域性最優的選擇?
對於任意一個站點:如果我們在這個站點加滿油,那麼最多就可以跑cmax*davg
的距離,我們對這個距離段中遇到的加油站情況進行分析:
- 按順序遍歷
【當前位置,當前位置+cmax*davg】
中的所有加油站,如果某個加油站的收費低於當前站點,那麼我就在當前站點加油,跑到那個站點去,加多少呢?就加能恰好讓我到達那個加油站的油。這樣我去那個站點加油就能更便宜。 - 如果當前站點後面有不止一個站點更便宜,怎麼選擇?比如我現在在
A
,價格是10
,後面是B
價格9
, 後面是C
價格8
, 我先找到的是B,那我就退出本次迴圈,剛好加油跑到B去,在B
處重新繼續分析。為啥不直接加油去C
,如果從當前位置直接加油去C
,那麼BC之間
的花費單價是當前加油站的價格也就是10
,但我如果先去了B
,那麼從B到C
的油價就是B
處的價格9
,顯然更便宜。這樣才滿足區域性最優。 - 如果當前位置後面沒有更便宜的加油站呢?
- 如果我在當前位置最多能達到的最遠距離超過了終點,那麼我直接加油跑到終點,因為後面的站點只會更貴。
- 如果我不能直接到終點,那麼我肯定是需要加油的,那我就找儘可能地找比較便宜的那個加油站,在當前加油站加滿油之後過去。既然沒有比當前更低價格的了,就讓油箱加到最大值,這樣能保證利益最大化,保證最大的距離使用的是便宜的油。
- 如果當前位置不能直接到達終點,並且後面沒有加油站了呢?那麼肯定不能到達中終點了,只能到達
當前位置+cmax*davg
,也就是說在當前位置加滿,能跑多遠是多遠。
總結:
- 當前位置能到達的範圍中如果存在更便宜的加油站,就加合適的油剛好到達那個加油站。
- 如果不存在更便宜的,但是當前位置能直接到達終點,那就加合適的油到達終點。
- 不存在更便宜的,並且不能直接到終點,找到可達的加油站的相對而言最便宜那個,在當前位置加滿油,然後去那個站點。
- 當前位置不能到終點,並且後面沒有加油站了,輸出能到達的最大距離。
程式碼
#include <iostream>
#include <algorithm>
using namespace std;
struct Station {
// 每單位汽油價格
double price;
// 從起點到這裡的距離
double dis;
}stat[500];
// 對加油站 離起點 從近到遠 排序
bool cmp(Station a, Station b) {
return a.dis < b.dis;
}
int main() {
// cmax油箱容量 d 距離 davg 每單位油能跑多遠 n個加油站
double cmax, d, davg;
int n;
cin >> cmax >> d >> davg >> n;
// 每個加油站的 價格 位置
for (int i = 0; i < n; ++i)
cin >> stat[i].price >> stat[i].dis;
// 根據離起點的距離排序
sort(stat, stat + n, cmp);
// 第一個加油站不在起點,無法起步
if(stat[0].dis != 0) {
printf("The maximum travel distance = 0.00");
return 0;
}
// nowpos當前在哪個加油站,
int nowpos = 0;
// nowgas 當前剩餘多少油,total_price 總花費
double nowgas = 0.00, total_price = 0.00;
// 油箱加滿一次油,可以跑多遠
double each_max_run_dis = cmax * davg;
// 是否到達終點
bool arrived = false;
while (!arrived) {
// 遍歷 【從當前加油站到最遠能跑到的位置】 之間的 全部加油站
bool exist_stat = false; // 當前位置後面是否存在可達加油站
bool exist_cheaper_stat = false; // 是否存在比當前位置更便宜的加油站
// 不存在比當前便宜的,就找到其中價格最低的
int min_pos = -1; double min_price = 9999.99;
for (int i = nowpos + 1; i < n; ++i) {
// 當前位置到不了這個加油站,退出迴圈
if (stat[i].dis - stat[nowpos].dis > each_max_run_dis) {
// 最多還能走 nowgas * davg
break;
}
exist_stat = true; // 存在可達加油站
// 如果有比當前加油站價格更低的加油站
// 算一下恰好跑到那個加油站需要多少油,
// 加油跑到那個加油站
if (stat[i].price < stat[nowpos].price) {
// 設定標誌
exist_cheaper_stat = true;
double needgas = (stat[i].dis - stat[nowpos].dis) / davg - nowgas;
// 加這麼多油,剛好跑到那個加油站,算一下花費
total_price += stat[nowpos].price * needgas;
// 到達那個位置後剩餘油量歸0
nowgas = 0;
// 改變位置
nowpos = i;
// 不再遍歷後面的加油站
// 比如我現在 在A,價格是10,後面是 B 價格9, 後面是C 價格8
// 我先找到的是B,那我就剛好加油跑到B去,在B處重新考慮
// 為啥不直接加油去C,如果從當前位置直接加油去C,那麼BC之間的花費單價是當前加油站的價格也就是10
// 但我如果先去了B,那麼從B到C的油價就是B處的價格9,顯然更便宜
// 這樣才滿足區域性最優
break;
}
// 如果說我能從當前位置跑1000米,但是在此之間的加油站的價格沒有一個比我現在的價格低
// 那我就儘量找最便宜的那個,然後在當前位置加滿油,跑到相對而言最便宜的那個加油站去加油
// 這樣才滿足區域性最優(在最便宜的位置加更多的油跑最多的距離)
// 這個if不會和上面的if同時執行(上面執行完就break了),所以不用加else
if (stat[i].price < min_price) {
min_pos = i;
min_price = stat[i].price;
}
}
// 不存在比當前便宜的,但是當前位置最遠能達到終點
if (!exist_cheaper_stat && (d - stat[nowpos].dis <= each_max_run_dis)) {
double needgas = (d - stat[nowpos].dis) / davg - nowgas;
// 加這麼多油,剛好跑到終點,算一下花費
total_price += stat[nowpos].price * needgas;
// 到達終點
arrived = true;
break;
}
// 不存在比當前便宜的,但是找到了其他加油站中相對最便宜那個
if (!exist_cheaper_stat && exist_stat) {
// 後面有加油站,但是都比當前位置的加油站貴
// 那我就儘量找最便宜的那個,然後在當前位置加滿油,跑到相對而言最便宜的那個加油站去加油
// 這樣才滿足區域性最優(在最便宜的位置加更多的油跑最多的距離)
double needgas = cmax - nowgas;
// 在當前位置加滿油,算一下花費
total_price += stat[nowpos].price * needgas;
// 到達那個位置後的剩餘油量
nowgas = cmax - (stat[min_pos].dis - stat[nowpos].dis) / davg;
// 改變位置
nowpos = min_pos;
// 當前位置無法抵達下一個加油站
} else if (!exist_stat){
// 最多還能走 cmax * davg
printf("The maximum travel distance = %.2f", stat[nowpos].dis + each_max_run_dis);
return 0;
}
}
// while正常結束,說明到達終點
printf("%.2f", total_price);
return 0;
}