1. 程式人生 > >動態規劃——一個神奇的演算法類

動態規劃——一個神奇的演算法類

當我第一次做到一個題,那是一個擁暴搜來解決的問題。原本只是想要簡單的看一下貪心法的演算法實現,沒想到竟然挖出了真麼個龐然大物。

在沒有看到他的程式碼實現之前,我一直以為計算機只是一個可以大量快速計算,但卻只能以二進位制,傻傻的算的機器。我沒要做的,便是把一切問題轉化為二進位制的計算形式,交給他來解決。因此在我的印象中,演算法挺無趣的。而動態規劃這種問題,顯然是隻有高智商,高情商的人類才能做得的高階思維活動啊。

知道了動態規劃之後,我才第一次深刻認識到計算機演算法的神奇與強大,雖然底層運算始終是0與1的加減替換,但簡單的0與1,也可以構造出如此人性化的計算方式。

好的,感慨到此為止,話不多說,以下貼題。

SDNU--1033.採藥

Description

辰辰是個天資聰穎的孩子,他的夢想是成為世界上最偉大的醫師。為此,他想拜附近最有威望的醫師為師。醫師為了判斷他的資質,給他出了一個難題。醫師把他帶到一個到處都是草藥的山洞裡對他說:“孩子,這個山洞裡有一些不同的草藥,採每一株都需要一些時間,每一株也有它自身的價值。我會給你一段時間,在這段時間裡,你可以採到一些草藥。如果你是一個聰明的孩子,你應該可以讓採到的草藥的總價值最大。”  如果你是辰辰,你能完成這個任務嗎?

Input

輸入的第一行有兩個整數T(1 <= T <= 1000)和M(1 <= M <= 100),用一個空格隔開,T代表總共能夠用來採藥的時間,M代表山洞裡的草藥的數目。接下來的M行每行包括兩個在1到100之間(包括1和100)的整數,分別表示採摘某株草藥的時間(1 <= t <= T)和這株草藥的價值(1 <= v <= 100000)。

Output

輸出包括一行,這一行只包含一個整數,表示在規定的時間內,可以採到的草藥的最大總價值。

Sample Input

100 5
77 92
22 22
29 87
50 46
99 90

Sample Output

133
當然,你可以說以採藥為題很東方,很玄幻(也許只是我個人感覺),但這是我遇到的最費解的題,因為他的動態。

首先題目給了你總時間T,和草藥總數M,每株草藥的價值和時間。當然資料不算很大,這個題考的不是時間複雜度,二是演算法實現,基礎的01揹包。(感謝強大的百度)這個題因為出現了兩個可供規劃的量,讓我想起了高中學的線性規劃。對付這類題,你需要的是找到一種邏輯上比較好理解,又容易實現和具體操作的演算法,來解決這種實際的問題。就在這時,我瞭解到了“揹包”。

這是一個多麼形象的比喻啊!

以下摘自百度百科————

揹包問題(Knapsack problem)是一種組合優化的NP完全問題。問題可以描述為:給定一組物品,每種物品都有自己的重量和價格,在限定的總重量內, 我們如何選擇,才能使得物品的總價格最高。問題的名稱來源於如何選擇最合適的物品放置於給定揹包中。相似問題經常出現在商業、組合數學,計算 複雜性理論、密碼學和應用數學等領域中。也可以將揹包問題描述為決定性問題,即在總重量不超過W的前提下,總價值是否能達到V?它是在1978年 由Merkel和Hellman提出的。 揹包問題已經研究了一個多世紀,早期的作品可追溯到1897年[1] 數學家托比亞斯·丹齊格(Tobias Dantzig,1884-1956)的早期作品[2] ,並指 的是包裝你最有價值或有用的物品而不會超載你的行李的常見問題。 (其實我發現這些題都是和古現當代數學問題有關的)這是一個好用的模版,你完全可以代入採藥。 而最關鍵的一部程式碼就在於———— f[j]=max(f[j-m[i]]+v[i],f[j]);

01揹包的狀態轉換方程 f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ),  f[i-1,j] }


So simple, so powerful! 乍一看你或許會覺得這和遞迴有些相似,但事實上他們根本不是一回事。遞迴是機械的,死板的,而動態規劃的奇妙,就在於動態。對於每一步,每一次甄選,都有兩個方向,隨資料的改變而改變。畢竟,在這種計算整體最優的題上,一步最優不是最優,在最後那一步達成之前,一切最優都是不確定的。 以我現在的水平,還不能很清晰的在腦海中重現他在計算機中的每一步實現與運算,但我可以認識到這種演算法有大用。 具體到這個題上來,在時間T內,採得最大價值藥草。一看就知道這個問題很複雜,就算是用人腦考慮,資料稍大也很麻煩。既然如此,我們只好嘗試倒推,來簡化邏輯。假設f[i,T]最大價值已經達成,那麼對於任意一個草藥,我們有2種選擇,放或者不放,而一旦揹包中一種草藥確定放入,那麼揹包中其他草藥所用最大時間  t 也就確定,在這個時間內最大價值f[i-1, t] 也就確定。對於這個草藥,我們只要判斷一下f[i-1,t] +v 與 f[i-1, T]的大小,就可以確定一個或除去一個草藥。n次之後,得出結果。邏輯如此,剩下的便是準備工作。 在以上邏輯中,我們需要對每一個草藥進行判斷,和幾乎每一個時間內的最大價值連遞得出,所以,外層n個草藥迴圈,每個草藥內層每個時間內判斷是否放入。因為時間量不同,對於每個草藥是否放入都有影響,所以我們只好把每個時間點上都計算出,畢竟動態規劃靠的就是資料量大,複雜度高,精細判斷。 以下是在下的程式碼,記得好像是借鑑了網上大佬的典例,畢竟我只是新手。 #include<cstdio>
#include<iostream>
using namespace std;
#define N 1005
#define M 105


int t,n;
int v[M],m[M],f[N];
int main()
{
    int i,j;
    scanf("%d %d",&t,&n);
    for(i=1;i<=n;i++)
        scanf("%d %d",&m[i],&v[i]);
    for(int i=1;i<=n;i++)
        for(j=t;j>=m[i];j--)
            f[j]=max(f[j-m[i]]+v[i],f[j]);//核心core
    printf("%d",f[t]);
    return 0;
}
最精煉的程式碼就是最好的程式碼,在我沒看揹包演算法自己嘗試的那些日子裡,我還以為會多麼繁雜,然而,just so-so 。 這是個動態規劃入門題,變式很多難度也更大,ACM之路與君共勉。 So far,thanks for your reading.