樹形DP和狀壓DP和揹包DP

2022-03-17 19:41:02 字數 2993 閱讀 7604

樹形\(dp\)和狀壓\(dp\)雖然在\(noip\)中考的不多,但是仍然是乙個比較常用的演算法,因此學好這兩個\(dp\)也是很重要的。而揹包\(dp\)雖然以前考的次數挺多的,但是現在基本上已經成了人人都能ak的題了,所以也不經常考了。

樹形dp

樹形dp這個非常特殊,他好像和是唯一乙個用深搜實現的dp,所以我們學好它也是應該的,其特點是通過深搜。

思路\(example\)

\(luogup2014\)(選課)這個題,就是乙個比較典型的樹形dp,順便還考察了一下分組揹包,

首先我們分析一下狀態我們可以設\(dp[i][j]\)表示以\(i\)的子樹中選\(j\)個(包括\(i\)自己)所得到的最大學分,因此我們可以採用樹形\(dp\)的方法。

首先預處理出\(dp[i][1]\)表示只選\(i\)自己所得到的學分,然後我們可以進行狀態轉移,因為題目給的輸入滿足\(0\)一定是唯一的根節點,所以最後我們只要輸出\(dp[0][m]\)首先我們要得到狀態轉移方程,然後仔細,細心的考慮邊界條件,這也是一般dp的思路,首先我們可以得出方程:

\(dp[i][j] = max(dp[i][t] + dp[son(i)][j - t])(j\in[1, ~m],t\in [1,j]]且son(i)要全列舉一遍)\),此時我們還要處理比較棘手的問題,就是轉移的順序,

首先這是乙個01揹包所以我們要\(for(j = m; j >= 1; j--)\)而t的範圍也是乙個坑點,因為在推到t時,我們首先要滿足比t小的一定要列舉出來,所以我們就要\(for(t = 1; t <= j; t++)\)

這個題基本上就解決了,**:

#include #include #include #include #include #include using namespace std;

vector son[100010];

int s[100010], out_degree[100010], in_degree[100010];

int dp[1000][1000];//表示i的子樹選j個課程

int n, m;

inline void dfs(int a)

}int main()

dfs(0);

printf("%d", dp[0][m + 1]);}/*

7 42 2

0 10 4

2 17 1

7 62 2

*/

狀壓dp

狀壓\(dp\)就更神奇了,可以說是最有\(oi\)特點的乙個\(dp\),因為它用到了位運算和二進位制。因此像那些乙個區間只有選或不選的操作的那些狀態可以用二進位制表示,然後在用一些不同尋常的東西,例如左移右移使可以狀態轉移。而判斷是否是狀壓dp時可以檢視資料範圍,如果在\(20\)以內就可以使用狀壓\(dp\)。

思路\(example\)

\(luogup1879\)也是乙個經典題,也經常被拿來用作寫狀壓dp的入門題,當然憤怒的小鳥也是乙個只要寫過幾個狀壓dp的就都能寫出來的演算法,因此也可以做一做,

我們分析一下玉公尺田這道題,題目讓我們求總共有多少種方案數,而且資料範圍還很小,這就在暗示我們採用狀壓\(dp\)的方法和套路。

預處理首先應該壓縮狀態,且基本上狀壓\(dp\)壓縮都是一樣的方法

壓縮狀態+預處理**:

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

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

scanf("%d", &flag[i][j]);//判斷土地是否肥沃

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

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

now[i] = (now[i] << 1) + flag[i][j];//壓縮狀態,狀壓dp的一般思路

尋找合理狀態

題目要求要滿足三個條件,即左右不能相鄰,上下不能相鄰,土地不能荒蕪,為了方便,我們先使其滿足左右不能相鄰。

滿足這個條件的前提是

(!(\(i\) & (\(i\)

<<1))) && (!(\(i\) & (\(i\)>>1)))​

就是該狀態左移和右移一位並與該狀態\(and\)的結果是零,即並沒有左移一位後的某一位與左移一位前的某一位相同,因為如果相同的話,就說明左右相鄰

尋找合理狀態**:

for (int i = 0; i < (1 << m); i++)

if ( (!(i & (i << 1))) && (!(i & (i >> 1))) )

check[i] = 1;//說明此狀態可行

狀態轉移

以上都是一行的預處理,現在我們要跨過這個界限,開始多行的轉移了,在進行多行的轉移的時候,就需要判斷第二個條件,就是上下之間不能有相鄰的狀態。

滿足這個條件的前提是

! (\(last\) & \(now\))

還需要判斷第三個條件,不能有土地是荒蕪的,這個也很好判斷,因為我們已經預處理出每一行的最難的滿足條件\(now[i]\), 如果(乙個狀態 & \(now[i]\)) == 該狀態,說明此狀態一定滿足不荒蕪,到此所有的條件都已經分析完畢,如果不懂可以手糊。

那就可以狀態轉移了,狀壓就成為了普通的二維dp了。

狀態轉移**:

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

for (int j = 0; j < (1 << m); j++)

if (check[j] && (now[i] & j) == j)//判斷此狀態可不可行,且不能有荒蕪的土地,

for (int k = 0; k < (1 << m); k++)

if (!(k & j) && check[k])//上下不能有相鄰的地方,比如如果上:10010,下:01100,那他們and的結果就不為0

dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mod;

樹形揹包DP

include using namespace std const int n 310,m n 2 int h n ne m v m idx int w n int dp n n int n,m void add int a,int b void dfs int u for int j m j 0 ...

狀壓dp 玉公尺田 狀壓dp

相關 強相關 327.玉公尺田 狀壓dp 小國王 狀壓dp 是井字形,本題是十字形。思路 狀態計算 時間複雜度 n 2 n 2n o n 22n 12 2 24n 2 n 2 n o n2 12 2 n 2n 2 n o n22n 12 224 看著妥妥超時,但是裡面合法狀態很少 依舊可以過 在此,...

NOIP模擬 乘積 狀壓dp 多組揹包

題目大意 選擇不超過k個n以內的正整數相乘,使乘積使乙個無平方因子數,問有多少種取法?每個數只能取一次 1 k,n 500 解題思路 首先可以想到把有平方因子數的數刪了。那問題也就變成了使取得的數的質因數集合無交集。如果質因數個數足夠少,我們就可以狀壓記錄每個質數取還是沒取,但500的範圍太大。不過...