樹的特徵
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...