專題訓練之dp

2021-10-20 11:17:01 字數 3621 閱讀 5251

五、本專題題解

我所理解的dp,就是要求多階段的問題解(或可以轉換為多階段的問題最優解),求出每個階段的解,最終推出答案。既將大問題轉換為乙個個小問題,並將這些小問題的答案儲存下來,在解決後面的小問題或者最終的大問題時可以用到。

剛開始接觸dp的時候,感覺和貪心區別不太大,兩者的寫法都基本相似(實際為貪心一般可用dp實現)。於是去問了師兄,師兄是這麼解答的:

貪心很多時候每一步是不相互影響的,dp是很多時候每一步之間會相互影響。對於會相互影響的,可能在dp的時候就要用到貪心的思想,記錄前面步驟最優的結果。然後貪心有時候也要用到dp來記錄前面的結果。

我的理解是貪心著重於當前最優解,既每乙個的最優;而dp則會看前面幾步的答案。

本菜菜沒有做太多題,但隊裡有巨佬總結了,這裡引用一下

1.線性dp

2.揹包問題

3.區間dp

4.樹形dp

5.狀壓dp

6.換根dp

7.概論dp

8.期望dp

9.數字dp

10.虛樹dp

11.斜率優化

12.單調佇列優化

13.插頭dp

14.凸優化dp

15.四邊形不等式

我認為解決dp問題的重點是狀態的設定,既如何構造狀態去表示所有的情況;有師兄在講課的時候說過,其實dp就是將所有暴力的情況,用很少的狀態去表示出來。我們拿到dp題的時候,所要想的就是怎麼在合理的時間複雜度之類,去構造狀態。

構造狀態不一定是直接構造出答案,既並不是說dp[n]/dp[n][n]就是最終的答案;例如專題練習中就有一題就是,求出dp[i],然後根據每個dp[i]去算出最後的答案。

在dp問題中,我們也要注意狀態的初始化,我就曾試過因為狀態初始化沒做好,導致一直wa

我們構造出狀態,並將狀態初始化之後,就要想如何實現狀態直接的轉換。一般而言,如果你對狀態的設定比較清楚,狀態的轉移也不成問題。

題目鏈結

思路:01揹包板子題

#include

#include

using

namespace std;

intmain()

,v[1010

],w[

1010];

int n,k,i,j;

cin>>n>>k;

for(i=

0;i) cin>>v[i]

;for

(i=0

;i) cin>>w[i]

;for

(i=0

;i)for

(j=k;j>=w[i]

;j--

) dp[j]

=max

(dp[j]

,dp[j-w[i]

]+v[i]);

cout<<}return0;

}

題目鏈結

題意:01揹包,並輸出選擇了哪些

思路:01揹包變型,另加乙個二維陣列,記錄在第i件物品,在價值j的時候是否使用了。最後遍歷一遍,輸出所用的物品。

#include

#include

using

namespace std;

intmain()

;for

(i=0

; i) cin>>v[i]

;int a[25]

[10010]=

;for

(i=0

; i)for

(j=n; j>=v[i]

; j--)}

for(i=m-

1,j=n;i>=

0;i--)}

cout<<

"sum:"

<<}return0;

}

題目鏈結

題意:01揹包,要求最小價值

思路:先全部賦值極大值,再每次比較取最小

#include

#include

#include

using

namespace std;

intmain()

,w[5100]=

,dp[

100100]=

;memset

(dp,

0x3f

,sizeof

(dp));

dp[0]

=0;for

(i=0

;i)for

(i=1

;i<=m;i++

)for

(j=0

;j) dp[j]

=min

(dp[j]

,dp[j-w[i]

]+v[i]);

if(dp[m]

!=0x3f3f3f3f

) cout<<

"the minimum amount of money in the piggy-bank is "

<<<

'.'

"this is impossible."

<}return0;

}

學習了乙個小技巧,memset用0x3f,可以將整個陣列賦值為極大值

題目鏈結

題意:現有面值為1、2、3、4、5、6的硬幣若干枚,現在需要知道能不能將這些硬幣分成等額的兩堆。

思路:多重揹包存在性問題 (比較複雜)

01揹包變型,可以將同樣的硬幣分成2的n次方個一堆,不能分的放一堆,然後再用01揹包來解。

#include

#include

using

namespace std;

intmain()

,dp[

100010]=

;int a[7]

=;for(i=

1;i<=

6;i++)if

(!p)

break

; t++

; cout<<

"collection #"

<':'

sum/=2

;int k=0;

for(i=

1;i<=

6;i++)if

(a[i]

>0)

v[k++

]=a[i]

*i;}

for(i=

0;i)for

(j=sum;j>=v[i]

;j--

) dp[j]

=max

(dp[j]

,dp[j-v[i]

]+v[i]);

if(dp[sum]

==sum)

cout<<

"can be divided."

"can't be divided."

<}return0;

}

2016 SCUT 專題訓練 簡單dp

link 從n件物品中搬走2 k 種,每次搬兩件,消耗的體力是兩手物品重量差的平方,對物品按重量排序之後,可以證明 搬相鄰重量的物品才能消耗最少,abcd四件物品重量非嚴格遞增,先搬bc再搬ad消耗的體力一定不會比先搬ab再搬cd小。很容易推出來的。所以dp k n 表示從前n個物品了搬了k對物品的...

訓練之DP 寒冰王座

寒冰王座 crawling in process.crawling failed time limit 1000msmemory limit 32768kb64bit io format i64d i64u submit status description 不死族的巫妖王發工資拉,死亡騎士拿到一張...

訓練之DP 數塔

數塔crawling in process.crawling failed time limit 1000msmemory limit 32768kb64bit io format i64d i64u submit status description 在講述dp演算法的時候,乙個經典的例子就是數塔...