1. 程式人生 > >[noip2007普及組]守望者的逃離-題解

[noip2007普及組]守望者的逃離-題解

描述

惡魔獵手尤迪安野心勃勃,他背叛了暗夜精靈,率領深藏在海底的娜迦族企圖叛變。守望者 在與尤迪安的交鋒中遭遇了圍殺,被困在一個荒蕪的大島上。為了殺死守望者,尤迪安開始對這 個荒島施咒,這座島很快就會沉下去。到那時,島上的所有人都會遇難。守望者的跑步速度為 17m/s,以這樣的速度是無法逃離荒島的。慶幸的是守望者擁有閃爍法術,可在1s內移動60m,不 過每次使用閃爍法術都會消耗魔法值10點。守望者的魔法值恢復的速度為4點/s,只有處在原地 休息狀態時才能恢復。 現在已知守望者的魔法初值M,他所在的初始位置與島的出口之間的距離S,島沉沒的時間T。

你的任務寫寫一個程式幫助守望者計算如何在最短的時間內逃離荒島,若不能逃出,則輸出守望 者在剩下的時間能走的最遠距離。注意:守望者跑步、閃爍或休息活動均以秒(s)為單位,且每 次活動的持續時間為整數秒。距離的單位為米(m)。

格式

輸入格式

在輸入檔案escape.in僅一行,包括空格隔開的三個非負整數M,S,T。

輸出格式

在輸出檔案escape.out包括兩行: 第1行為字串“Yes”或“No”(區分大小寫),即守望者是否能逃離荒島。 第2行包含一個整數。第一行為“Yes”(區分大小寫)時表示守望者逃離荒島的最短時間; 第一行為“No”(區分大小寫)時表示守望者能走的最遠距離。

直接進入重點吧。

事實一:假設存在一個最優解(就是在一定時間內跑得最遠),那麼在時間軸上畫出來大概像下面這個模樣:

 對於1,因為我只是改變了動作的順序而已,原來是最優解肯定魔法就夠啦,所以我換一下順序,把休息的動作全部提到最前肯定魔法也是夠的;對於2,用腳趾想也知道距離肯定不會變。

這個事實說明了所有的最優解都可以用這樣一個固定的模式構造出來:先跑,再停,最後的時間用來閃(當然不排除其他最優解,但我只需要一個就好了,這樣能成為最優解就OK)。所以,我令f[t]表示t時間內最遠能跑多遠,再設t-i表示跑的時間,那麼i就是停加閃的時間了。這裡要來一個貪心,但超級簡單。就是這樣一個情形,你必須儘可能的少停,而是要多閃(因為時間之和是一定的,所以只要魔法夠就要閃),這樣才能跑的遠,下面,我令Z(i)表示只停和閃,i時間內最遠跑多遠。式子在下面:

至此,整個問題就圓滿解決了,這個問題關鍵是要弄清楚答案的結構,三段,然後就很容易想清楚後面一段的貪心演算法,但是跑的時間是不能確定的,因此只能動規,在遞推的時候,把t寫成t+1,就得到了可以用f[t]表達的子表示式,於是化簡減小複雜度,問題解決。其實完全不需要在這裡放程式碼的,但是還是放一個可行答案供大家參考指正吧。其實我覺得程式思路最重要,如果連複雜度都一樣,那還是越簡單、越容易理解越好。

#include <cstdio>
#include <iostream>
using namespace std;
int m,s,t;
int z[300005],f[300005];
void getz() {
    for (int i=1;i<=t;i++) {
        if (m>=10) {
            z[i]=z[i-1]+60;
            m-=10;
        }
        else {
            z[i]=z[i-1];
            m+=4;
        }
    }
}
int main() {
    scanf("%d%d%d",&m,&s,&t);
    getz();
    for (int i=1;i<=t;i++) {
        f[i]=max(f[i-1]+17,z[i]);
        if (f[i]>=s) {
            printf("Yes\n%d",i);
            return 0;
        }
    }
    printf("No\n%d",f[t]);
    return 0;
}