這篇博客主要講的是動態規划入門,即動態規划的思想,並且再講解動態規划的最簡單的一個方法。
首先,什么是動態規划?
動態規划是通過拆分問題,定義問題狀態和狀態之間的關系,使得問題能夠以遞推(或者說分治)的方式去解決。其實就是分解問題,分而治之。可能這樣說大家都不太理解,其實這個有點類似於數學中的遞推公式。來舉一個簡單的例子,看下邊這個題:
N階樓梯上樓問題:一次可以走兩階或一階,問有多少種上樓方式。
這就是動態規划就簡單的一個列子。拿到這個題,大家是不是都有點迷,這個到底怎么做?是不是沒有思路,那么按照動態規划思想,我們可以先分析下問題,每次都有兩種跳法,分別是一階或者兩階,那么如果當前是第n個台階,那么跳法是不是是(n-1)台階的跳法數加上(n-2)台階的跳法數?如果划算成公式是F(n) = F(n-1)+F(n-2)。F(n)代表第n階台階的跳法的數量。
這不就相當於找到了一個遞推公式,然后來進行計算。具體的代碼實現如下(采用的是非遞歸):
#include <stdio.h> int main() { int i,N; long long a[90]; while(~scanf("%d",&N)) { a[1]=1; a[2]=2; for(i=3;i<=N;i++) a[i]=a[i-1]+a[i-2]; printf("%lld\n",a[N]); } return 0; }
下邊再舉一個列子來輔助理解:
n封信放入n個信封,要求全部放錯,共有多少種放法,記n個元素的錯排總數為f(n)
對於這個題,我們可以采用和上述一樣的思路,我們是否要考慮下找一個類似的遞推公式等來解決。思路如下:
在任意一種錯裝方案中,假設n號信封里裝的是k號信封的信,而n號信封里的信則裝在m號信封里。我們按照k和m的等值與否將總的錯裝方式分為兩類。 若k不等於m,交換n號信封和m號信封的信后,n號信封里裝的恰好是對應的信,而m號信封中錯裝k號信封里的信,即除n號信封外其余n-1個信封 全部錯裝,其錯裝方式等於 F[n - 1],又由於m的n-1個可能取值,這類錯裝方式總數為(n - 1)* F[n - 1]。也可以理解為,在 n-1個信封錯裝的 F[n - 1]種方式的基礎上,將n號信封所裝的信與n - 1個信封中任意一個信封(共有 n-1 中選 擇)所裝的信做交換后,得到所有信封全部錯裝的方式數。另一種情況,若 k 等於 m,交換 n 號信封和 m 號信封的信后,n 號信封和 m 號信封里裝的恰好是對應的信,這樣除它們之外剩余的 n-2 個信封全部錯裝,其 錯裝方式為 F[n - 2],又由於 m 的 n-1 個取值,這類錯裝方式總數為(n - 1)* F[n - 2]。也可以理解為,在 n - 2 個信封全部錯裝的基礎上,交換最后兩個信封中的 信(n 號信封和 1 到 n-1 號信封中任意一個,共有 n-1 種選擇),使所有的信封全部 錯裝的方式數。 綜上所述,F[n] = (n - 1) * F[n - 1] + (n - 1) * F[n - 2]。這就是錯排公式。
具體代碼如下:
#include <stdio.h> Long long F[21]; //數值較大選用long long int main () { F[1] = 0; F[2] = 1; //初始值 for (int i = 3;i <= 20;i ++) F[i] = (i - 1) * F[i - 1] + (i - 1) * F[i - 2]; //遞推求得數列每一個數字 int n; while (scanf ("%d",&n) != EOF) { printf("%lld\n",F[n]); //輸出 } return 0; }