樹形DP簡單總結

2021-08-21 10:51:04 字數 4631 閱讀 4208

樹的特徵

1.n個點 只有n-1條邊的無向圖

2.無向圖里 任意兩點有且只有一條路

3.乙個點只有乙個前驅 但可以有多個後繼

4.無向圖沒有環

樹形dp

由於樹有著天然的遞迴結構 父子結構 而且它作為一種特殊的圖 可以描述許多複雜的資訊 因此在樹就成了一種很適合dp的框架

問題:給你一棵樹 要求用最少的代價(最大的收益)完成給定的操作

樹形dp 一般來說都是從葉子從而推出根 當然 從根推葉子的情況也有 不過很少(本蒟蒻還沒有做到過~)

一般實現方式: dfs(包括記憶化搜尋),遞推等

例題

1.二叉蘋果樹

傳送門

二叉樹 很爽的一種dp結構 由於二叉樹父親節點只用管它的左右兒子 狀態轉移變得較為輕鬆

在遇到多叉樹時 我們時常會考慮把多叉樹轉化為二叉樹來做

而這道題直接是二叉樹

首先考慮給dp陣列下定義一般來說樹形dp的dp陣列的第一維都是當前節點的編號

這道題光一維肯定是不夠的  那麼加維 發現dp[i][j]表示當前節點為i 保留j個節點的最大蘋果數量比較ok

很明顯 該問題具有很明顯的最優子結構性質也具備

無後效性(每一步只與兒子有關係 而與爸爸之類的沒有關係 )

另外 還可以在dfs時運用記憶化 可以大大提高速度 

再提一句 由於題目中給的權值在邊上 讓人特別難受 於是 我們把權值轉化到兒子上會方便操作

//f[i][j] 當前在i點 保留j個節點 

//f[i][j]=max(f[i][j],f[tree[i].l][k]+f[tree[i].r][j-k-1]+tree[i].v);

#includeusing namespace std;

const int n=150;

int n,q,dp[n][n];

struct node

tree[n*20];

int dfs(int now,int point)

if(tree[now].lson==0&&tree[now].rson==0)

if(dp[now][point]>0) return dp[now][point];//記憶化

for(int k=0;k>n>>q;

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

cout傳送門

這道題就是採用剛才提到過的 把多叉樹轉化為二叉樹來做

關於如何把多叉樹轉化為二叉樹 有個口訣 叫做左兒子不變 右兒子兄♂弟 

等轉化為二叉樹了過後 讓我們來琢磨一下

左兒子:原根節點的孩子

右兒子:原根節點的兄♂弟

也就是說 不能直接套用第一題的方程 但是可以對dp陣列進行相同的定義

對於乙個根節點 都可以 選 或者 不選

當給左兒子分配資源時 根節點必須選 而與右兒子無關

因此 方程就顯而易見了 dp[i][j]=max(dp[i][j],dp[i.rson][j],dp[i.lson][k]+dp[i.rson][j-k-1]+val[i])   (0<=kusing namespace std;

const int n=305;

int n,m,bigson[n],dp[n][n];

struct node

tree[n*4];

int dfs(int now,int point)

cout<3.樹的直徑

傳送門

這是解析...然而我覺得bfs或者dfs就夠了 何苦dp

4.戰略遊戲

傳送門假設dp[i]表示以i為根的子樹上需要安放的最少士兵 希望能從i的兒子推出i的情況

然而無法做到 考慮加維

由於每個節點可以選 或者不選 

如果選了的話  那他的兒子可選可不選

如果沒選的話 那他的兒子就必須選

因此dp[i][0]表示選了節點i所需要安防的最少士兵 dp[i][1]表示不選

方程顯而易見 dp[i][0]=sigma min(,dp[i.son][0],dp[i.son][1]) dp[i][1]=sigma min(dp[i][1],dp[i.son][0])

//dp[i][0] 選i dp[i][1] 不選i 的所需最小個數

//如果選了i 意味著可以選或者不選他的兒子

//如果沒選 意味著必須選所有的兒子

//dp[i][1]=sigma(dp[i.son][0])

//dp[i][0]=sigma(min(dp[i.son][0],dp[i.son][1]))

#includeusing namespace std;

const int n=1505;

int n,dp[n][2],first[n],tot;

struct edge

edge[n*4];

inline void addedge(int x,int y)

inline void dfs(int now)

}int main()

} dfs(0);

cout<5.皇宮看守

**實在沒找到....我提交的地方是學校題庫

(題面)

對於每個點 都有三種情況

1.自己放

2.父親放(被父親看到)

3.兒子放(被兒子看到)

這意味著什麼呢?

對於乙個i 

if 自己放了 也就是說兒子一定被父親看到 也可以安排警衛 也可以被它的兒子看見

else

如果父親放了 也就是說兒子可以安♂排 也可以被它的兒子看見

如果兒子放了 它的兒子必定有乙個安排了的 否則被它的兒子看見 具體可以進行一些 特♂判

其實這道題很像上一道題的公升級版

點到為止 不多說了(其實只是懶)

6.訊息傳遞

傳送門

由於根是不一定的 所以需要遍歷所有點 作為根

設dp[i]是以i為根的子樹傳遍它所有子樹需要的最少時間

dp[i]取決於花費時間最多的那顆子樹(當然還要加上每次一秒的傳遞時間) 不過也不是一定的 萬一話費時間最多的和次多的只差了一秒之類的情況也會出現 所以需要遍歷所有的兒子~

方程:dp[i]=max

#includeusing namespace std;

const int n=3005;

int n,tot,first[n],dp[n],son[n],cnt,ans=0,num;

struct edge

edge[n*10];

inline void addedge(int x,int y)

inline bool cmp(const int &a,const int &b)

inline void dfs(int now,int fa)

int cnt=0;

for(int u=first[now];u;u=edge[u].next)

sort(son+1,son+cnt+1);

int ret=0;

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

dp[now]=ret+1; //加1是因為仔細看了樣例後發現預設時間是從一秒開始的orz

}vector con;

int main()

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

(j>k>=1,)

#includeusing namespace std;

const int n=3005;

int n,m,first[n],pay[n],tot,dp[n][n];

struct edge

edge[n*2];

inline void addedge(int x,int y,int z)

int dfs(int now)

int j=0;

for(int u=first[now];u;u=edge[u].next)

}} return j;

}int main()

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

memset(dp,128,sizeof(dp)); //128是負無窮大

dfs(1);

for(int i=m;i>=0;i--)

{ if(dp[1][i]>=0)

{ cout<

總結:通常來說 把一棵樹轉化為二叉樹 然後整個問題的最優只涉及到左右兒子的最優 然後考慮根節點隨之的變化 這樣化簡了問題 也很容易推出狀態轉移方程 

當然 也不是所有問題都要這樣 我們應該仔細推敲每個結點的狀態 以及相應狀態與父子結點的聯絡等 就是如何從子節點的最優值推出父節點的最優值

樹形dp總結

from 列出一些經典問題吧 1 給出一棵樹 每個節點有權值 要求父節點和子節點不能同時取 求能夠取得的最大值 hdu1520 2 給出一棵樹,求離每個節點最遠的點的距離 hdu2196 3 1 在乙個地圖上,有n座城堡,每座城堡都有一定的寶物,在每次遊戲中允許攻克m個城堡並獲得裡面的寶物。但由於地...

樹形dp總結

這個月一直搞dp了,狀壓,數字,樹形,感覺雖然有時訓練很辛苦,但真的很充實。這個星期看了一些樹形dp的資料。樹形dp簡單來說就是在樹上的dp,這裡的很多題,都和揹包有聯絡,從乙個根節點開始,分配方案給它的子樹。有乙個很有意思的題。沒有上司的聚會 hdu 1520 大致題意就是說,要舉辦乙個聚會,每個...

樹形DP總結

換根 fat結點更新u結點子結點 dp fat ans fat max dp u 0 老方法 更新根節點 ans u dp u max dp fat 0 第一次dfs 回溯時處理子結點為u向下的簡單路徑第一大和第二大 第二次dfs 遞迴處理子結點為u向上的簡單路徑最大 const int n 5e5...