學習筆記 動態規劃 I 初識DP

2022-04-29 20:39:12 字數 3127 閱讀 5838

更新日期:2018/3/16,2018/12/03

動態規劃,簡稱dp(dynamic programming)

簡介1簡介2

動態規劃十分奇妙,它可以變身為記憶化搜尋,變身為遞推,甚至有時可以簡化成乙個小小的算式。

動態規劃十分靈活,例如 noip2018 pj t3 擺渡車 ,寫法有很多很多,但時間、記憶體卻各有差異。

動態規劃十分簡單,有時候乙個小小的轉移方程就能解決問題。

動態規劃十分深奧,有時你會死也想不出合適的轉移方程,有時你會被後效性困擾,有時動態規劃的同時還有許多蜜汁優化。

動態規劃在noip中十分重要,我目前為止參加的\(noip_ \& noip_\)都有一道動態規劃,而且都是\(t3\)。(估計普及考綱比較窄,要出難題只有dp了)

還是這道題...... 數塔問題!!!

這裡我們選擇動態規劃來解決.

我們不難理解,對於每乙個元素,它到頂層的最大值是確定的,也就是說,從頂層到任何乙個元素的最大值都是確定的.比如,對於第3層的第2個元素6,頂層到它的最大值只有乙個(9 + 15 + 6 = 30)(但不代表路徑只有一條),不會改變.

所以,我們用乙個陣列dp來儲存從元素(i, j)到底層的最大值.

#define maxn 100

int dp[maxn + 5][maxn + 5];

仔細觀察分析,不難發現,對於每乙個元素dp[i][j],都存在

dp[i][j] = max( dp[i + 1][j], dp[i + 1][j + 1] ) + a[i][j];
即每乙個元素到(1, 1)的最大值都是上一層與它相連的兩個元素中較大的乙個,再加上這個元素本身的值. 最後的答案即為dp[1][1].

不過,我們自頂向下分析,但是卻要自底向上實現,即從最頂層開始分析,寫**時卻要注意for語句要倒過來寫:

for ( int i = n; i >= 1; --i )

for ( int j = 1; j <= i; ++j )

dp[i][j] = max( dp[i + 1][j], dp[i + 1][j + 1] ) + a[i][j];

為什麼會這樣呢?其實不難分析,在算dp[i][j]時,你必須確保dp[i + 1][j]dp[i + 1][j + 1]已經完成,如果沒有完成,dp[i + 1][j]dp[i + 1][j + 1]的值就是錯誤的,算出的dp[i][j]也是錯誤的,這樣結果就不對了。而反過來做,你就會發現i從大的開始,在做dp[i][j]的時候dp[i + 1][1 ~ n]都已經做過了。還有,要注意,動態規劃的初始化很重要,有時初始化就會決定你結果對不對。這裡的初始化很簡單,現在給出兩種方法:

memset( dp[n + 1], 0, sizeof( dp[n + 1] ) );//即把dp[n + 1][0...]全部初始化為0.

for ( int i = 1; i <= n; ++i )

dp[i] = a[i];

//下面這個與上面等價:

copy( a[n] + 1, a[n] + n + 1, dp[n] );// copy( 開始位址, 結束位址, 放到的陣列 ); copy( a, a + n, b );即為把a陣列下標為0~n按次序複製到b陣列.

//當然,這樣寫,實現時要注意少一層迴圈:(下面這個是修改後的)

for ( int i = n - 1; i >= 1; --i )

for ( int j = 1; j <= i; ++j )

dp[i][j] = max( dp[i + 1][j], dp[i + 1][j + 1] ) + a[i][j];

//至於為什麼這樣,這裡不再贅述,請自己思考.

這裡再完整地放一放**,實在不會寫的可以參考.

#includeusing namespace std;

#define maxn 100

int c, n;

int a[maxn + 5][maxn + 5];

int dp[maxn + 5][maxn + 5];

void solve()

int main()

事實上,可以做乙個優化:去掉dp陣列,直接用a陣列來做:(節約空間,人人有責)

#includeusing namespace std;

#define maxn 100

int c, n;

int a[maxn + 5][maxn + 5];

void solve()

int main()

至於為什麼,請諸位自己理解(很好理解的,選個小一點的資料自己算一算就知道了)。

怎麼樣,找到些感覺了吧?現在我們來學習怎麼寫動態規劃的程式.

第一步,我們要觀察題目是否可以用動態規劃實現。怎麼判斷呢?我們要看它是否可以分成幾個階段,如上題,可以分成1~n層共n個階段,每個階段還可以分成1~i個元素共i個小階段。然後,我們要看看每個階段的答案是不是確定的,上題中,每乙個元素到底層的最大值就是確定的。再看看每個階段是不是有關聯,如果有,還要確定有什麼關聯,是否對於每乙個階段都滿足。

第二步,就是確定關聯啦。怎麼確定呢?我們要仔細分析題目,觀察每兩個階段之間的關係。動態規劃的重點也就在這裡,關聯確定了,動態規劃基本上就可以寫下來了。

第三步,確定邊界條件,比如,上題就要把dp[n+1][...]全部賦值為0,否則就會出錯。

除此之外,還要確定完成的順序,要做某個階段,它需要用到的階段必須先做完。

當然,有時還要新增滾動陣列、優化等。

這樣,乙個動態規劃程式就完成啦。

當然,動態規劃還有許多分支(揹包dp、區間dp等),以上講的都是最表皮的。那些難一點的,都只好下次再講吧。

最好拿點題目來練一下:洛谷的dp

動態規劃學習系列 數字DP(初識)

第一次知道數字dp這東西,是在大二新手賽,那時有一道 cutting trees 的題目,現在來看就是水題一道,可以用多種方法水過,可惜當時愣是沒做出來,其他水題也沒做出來,於是被大一虐成翔。抱著學習的態度,我們再來看看這道題。多組詢問,每組詢問a和b,為 a,b 範圍內,有多少個數是由乙個上公升序...

DP(動態規劃)學習筆記

揹包問題 01揹包每件物品最多使用一次 完全揹包每件物品有無限個 多重揹包每種物品最多有si個 存在樸素版和優化版 分組揹包沒組最多只能選 1 個 dp優化 對dp方程進行等價變形 dp最重要的就是公式推導 對於當前狀態的計算 要滿足兩個條件 不重 不漏 最簡單的01揹包 d p i j ma x ...

動態規劃初識

適合用動態規劃的問題特徵 可以分解成相互重疊的若干子問題 滿足最優性原理 結構性質 該問題的最優解中也包含著其子問題的最優解。一般地,子問題的聯絡體現在某種遞推關係,通過這種遞推計算可以把問題的解儲存起來,後期直接使用,避免重複運算。簡單的dp例子 遞推關係 m i j m i 1 j m i j ...