1. 程式人生 > >藍橋杯 旅行家的預算 By Assassin [複雜的貪心]

藍橋杯 旅行家的預算 By Assassin [複雜的貪心]

問題描述
  一個旅行家想駕駛汽車以最少的費用從一個城市到另一個城市(假設出發時油箱是空的)。給定兩個城市之間的距離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

思路:還是要先吐槽一下,好多網上的教程都是錯的啊,什麼直接走到頭然後不能走了在當前的加油站加上油到下一個加油站就行,感覺根本就是欺負藍橋杯的資料規模。事實上這個題完全想清楚還是不太容易的。下面講題。

我們先分析資料知道,加油站有位置,油價。然後車的油箱有容量,猛一想並不是直接就能相通如何找到停留點的,具體看下面的分析。

我們先分析一下需要什麼變數,車有可能有油,有可能沒油,所以需要一個變數記錄油箱剩下多少油。然後我們想一下,如果當前在第i個加油站,而只有行駛到第i+k個加油站油價更便宜,那麼肯定停下在i+k點加油比較合算,那麼我們肯定就要記錄每次停下的位置(因為後面分析是貪心,貪心的思想就是將一個線段分成幾份走完,區域性最優求整體最優。)

首先確定如何判斷無法到達目的地的情況:我們知道 車輛不加油的最大行駛距離== (油箱容量)c * (每升油的行駛距離)d2 那麼當存在相鄰兩個加油站的間距比車輛不加油的最大行駛距離還大的話,到目標點是不可能完成的。

然後我們分析一下車輛的行駛情況

1.假如當前點為i,在車輛不加油的最大行駛距離記憶體在第i+k處油價更低

(1)油箱內剩餘的油可以到達該位置

這種情況我們直接用剩下的油跑到i+k點就可以了,不花錢肯定是最節省的!

(2)油箱內剩餘的油不夠到達該位置

在這種情況是肯定是要加油的,而且無疑我們肯定是加當前點i的油是最划算的。那麼應該加多少呢?加上油剛剛好到達i+k點是最好的!

因為i+k的油比i的油便宜,沒必要在i位置多加油,如果後面需要在i+k加更划算。

2.假如當前點為i,在車輛不加油的最大行駛距離內不存在第i+k處油價更低

這個時候怎麼辦!我們加滿油去跑一定划算,因為以i為起點在車輛不加油的最大行駛距離內 行駛一定找不到價格更低的加油站,我們一定是在價格相對最低的地方加油比較好。具體加油的多少還要看下一步能不能到更便宜的加油站,如果有參考1行進,如果沒有繼續參考2行進。

然後我的程式碼有一些特點,首先變數名比較長啊…海涵…然後是用結構體記錄的第0位置是起點,第n+1位置是終點,終點的油價變數我設為0,可以理解為最便宜的,所以程式一定回到n+1點去!為什麼說這個在程式實現的過程中就能體會到了。

我的具體程式碼基本上都註釋了,如果有不明白的歡迎提問!

下面是我的醜程式碼:

#include<bits/stdc++.h>
using namespace std;
typedef struct node{
    double pos;
    double price;
}node;
node a[100001];
double last_oil;        //油箱剩餘空間
double run_c;           //剩餘油料可以行走的最大距離
int    best_pos;        //形成內最小油料價格的位置 
double full_run;        //郵箱在滿了的情況下可以跑多遠 
double use=0;
double d1,c,d2,p;
int n;
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].pos-a[best_pos].pos<=full_run){             //必須在full_run範圍之內 
            if(a[i].price<=a[best_pos].price){              //如果是更優的選擇 
                flag=1;                                     //標記 
                if(run_c>=a[i].pos-a[best_pos].pos){        //如果剩下的油已經夠跑了 
                    last_oil=(a[i].pos-a[best_pos].pos)/d2; //剩餘油量更新
                }
                else{                                       //如果剩下的油不夠跑 
                    last_oil=0;                             //要恰好跑到價格更低的加油站 
                    use+=(a[i].pos-a[best_pos].pos-run_c)/d2*a[best_pos].price;
                }
                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].pos-a[best_pos].pos<=full_run){
                if(a[i].price<a[better_pos].price){
                    better_pos=i;
                }
            }
        }
        //找到了更優值
        use+=(c-last_oil)*a[best_pos].price;                //一定是加滿油最好了!因為在滿油的行車範圍內都到不了最優的,所以一定要加滿油 
        last_oil=c-(a[better_pos].pos-a[best_pos].pos)/d2;  //先更新到達better_pos位置剩下的油料 
        best_pos=better_pos;
    } 
    run(); 
}

int main(){
    //freopen("input.txt","r",stdin);
    cin>>d1>>c>>d2>>p>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i].pos>>a[i].price;
    }
    a[0].pos=0;             //出發點 
    a[0].price=p;
    a[n+1].pos=d1;          //終點 
    a[n+1].price=0;

    last_oil=0;             //初始化郵箱剩餘油量 
    best_pos=0;             //初始化初始位置 
    full_run=c*d2;

    for(int i=0;i<=n;i++){  //驗證是否可達 
        if(a[i+1].pos-a[i].pos>full_run){
            cout<<"No Solution"<<endl;
            return 0;
        }
    }

    run();

    printf("%.2lf\n",use);
    return 0;
}