1. 程式人生 > >程式設計四連測之第一測[telephonewire]

程式設計四連測之第一測[telephonewire]

題目

題目描述

最近,約翰的奶牛們越來越不滿足於牛棚裡一塌糊塗的電話服務,於是,她們要求約翰把那些老舊的電話線換成效能更好的新電話線。新的電話線架設在己有的n根電話線杆上,第i根電話線的高度為hi, ( 1 <= hi<= 100)。電話線總是從一根電話線杆的頂端被弓}到相鄰的那根的頂端,如果這兩根電話線杆的高度hi和hj不同,那麼約翰就必須支付c * |hi - hj|的費用,當然,你不能行動電話線杆,只能按照原有
的順序在相鄰杆間架設電話線。

加高某些電話線杆能減少架設電話線的總費用,儘管這項工作也需要支付一定的費用。更準確的說,如果他把一根電話線杆加高x米的話,他需要付出x^2費用。

請你幫約翰計算一下,如果合理的進行這兩項工作,他最少要在這個電話線改造工程中花多少錢。

輸入

第一行輸入兩個數n (n <= 100000)和c (c <= 100), 含義如上。接下來n行,每行一個整數hi (hi < 100)。

輸出

輸出約翰完成電話線改造工程需要花費的最小費用,保證答案不超過max long int。

樣例輸入

5 2
2
3
5
1
4

樣例輸出

15

解題思路

DP

這個題一看就應該是dp吧,但是是幾維呢?我覺得不太可能會有人認為這是三維吧(就這資料的大小怎麼都會MLE)。

令dp[i][j] 為第i根電線杆高度為j時的最小費用,則可以得到 dp[i][j] = min \left \{ dp[i - 1][k] + \left | j - k \right | * c + (j - h[i])^{2} \right \}

其中的k是列舉前一個電線杆的高度。

看起來很複雜?沒事,我們來逐一分解。

可以看到,\left ( j - h[i] \right )^{2} 是跟當前狀態有關,意思也就是說它不參與k的運算,可以獨立在外面。這下就變成了:dp[i][j] = min \left \{ dp[i - 1][k] + \left | j - k \right | * c \right \} + (j - h[i])^{2}

繼續來分,我們現在來把絕對值去掉,就會有兩種情況:①j >= k,就會變成dp[i][j] = min \left \{ dp[i - 1][k] - k * c + j * c \right \} + (j - h[i])^{2},再把j * c拿出來,就是min\left \{ dp[i - 1][k] - k * c \right \} + (j - h[i])^{2} + j * c

②j <= k,就會是dp[i][j] = min \left \{ dp[i - 1][k] + k * c - j * c \right \} + (j - h[i])^{2},拿出來,min\left \{ dp[i - 1][k] + k * c \right \} + (j - h[i])^{2} - j * c

這下子,所需要參與運算的,也就是說要藉助以前的狀態的就只剩下k了。

列舉方向

這裡有兩種可能性,一種j >= k,就相當於是在j以前計算,那麼如果說是從小到大計算的話,那麼就可以藉助他的上一個來求解,也就是說dp[i - 1][k]可以藉助dp[i - 1][k - 1]來得到。也就相當於,如果你要求dp[i - 1][k]的話就需要k次迴圈來求了,但是dp[i - 1][k - 1]是已經求了前面k - 1次,只需要藉助它,就只需要O(1)的複雜度。

同理,j <= k的話就應該是從大到小來計算了。

變數充當單調棧

可以從上面看到,如果需要單調棧的話,那麼只需要棧頂的元素就夠了,可是此時棧頂的元素是什麼呢?很顯然就是dp[i - 1][k - 1],可是在用了棧頂之後,就又需要把它彈出去,然後把dp[i][k]裝進去。

形象點說,也就是說每個棧頂只會被用到一次,用完之後就又被踢出去了。

那這樣的話,還不如就用一個變數來充當棧頂來儲存上一輪中的最小值算了。

參考程式碼

/*
這道題可以用dp求解。令dp[i][j] 為第i顆電線杆的高度為j時i前(包含i)的最小值。
可以得到轉移為 dp[i][j] = min(dp[i - 1][k] + |j - k| * c)
k是列舉i - 1顆的高度,但這裡有一個問題,到底是j大還是k大,所以我們需要分兩種情況:
1、j >= k,dp[i][j] = min(dp[i - 1][k] - k * c + j * c),在j ~ 1中間列舉最小值。
本來應該是用單調佇列列舉最小值,但可以看出,此時最小值只跟i - 1的情況有關,所以可以用變數代替。
2、j <= k,dp[i][j] = min(dp[i - 1][k] + k * c - j * c),在j ~ 最高高度列舉。
這裡之所以是列舉到最高高度,是因為沒有必要在超過最高高度了。
同時也用一個變數列舉這之間的最小值
*/
#include <cstdio>
#include <cstring>
using namespace std;
#define MAXN 100000
#define INF 0x3f3f3f3f
#define max(a, b) a > b ? a : b
#define min(a, b) a < b ? a : b
 
int n, c, h[MAXN + 5], maxh, ans = INF;
int dp[MAXN + 5][105];
 
void read (int &x){
    x = 0;
    char c = getchar ();
    while (c < '0' || c > '9')
        c = getchar ();
    while (c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + c - 48;
        c = getchar ();
    }
}
 
void print (int x){
    if (x < 0){
        putchar ('-');
        x = (~x) + 1;
    }
    if (x / 10)
        print (x / 10);
    putchar (x % 10 + 48);
}
 
int main (){
    read (n); read (c);
    for (int i = 1; i <= n; i++){
        read (h[i]);
        maxh = max (maxh, h[i]);
    }
    memset (dp, INF, sizeof (dp));
    for (int i = h[1]; i <= maxh; i++){//考慮邊界,dp[1]的情況很單一
        dp[1][i] = (i - h[1]) * (i - h[1]);
    }
    for (int i = 2; i <= n; i++){
        int min1 = INF, min2 = INF;
        for (int j = 0; j < h[i]; j++){//算出從0到j - 1的最小值,即是上面的1情況
            min1 = min (min1, dp[i - 1][j] - j * c);
        }
        for (int j = h[i]; j <= maxh; j++){
            dp[i][j] = min (dp[i][j], min1 + (j - h[i]) * (j - h[i]) + j * c);
            min1 = min (min1, dp[i][j]);//因為從下到上考慮,所以dp[i][j]也要“放進佇列”考慮一下
        }
        for (int j = maxh; j >= h[i]; j--){
            min2 = min (min2, dp[i - 1][j] + j * c);
            dp[i][j] = min (dp[i][j], min2 + (j - h[i]) * (j - h[i]) - j * c);
        }
    }
    for (int i = 1; i <= maxh; i++){
        ans = min (ans, dp[n][i]);
    }
    print (ans);
}
 

當然,這裡有心的人也許會看到,整個dp陣列實際上只跟i和i - 1youg有關,那麼完全可以用一個滾動陣列來求解。

不過這裡就先不粘滾動陣列的程式碼了。有興趣的可以看這裡