DP學習筆記——樹形DP
阿新 • • 發佈:2020-09-16
一、樹上揹包
在有依賴關係的揹包問題中,依賴關係通常可以構成森林。建立一個超級源點\(0\)並設為整體的根節點,則可以將森林組織成一棵樹。對於樹中任意一個節點\(v\),要將它放入揹包,必須先將其父節點\(u\)放入揹包。
\(dp[i][j]:=\) 以 根節點為節點\(i\)的的子樹 為物品選擇範圍、在揹包容量為\(j\)時的最大價值。
常見解法是,通過DFS遞迴到葉子節點(假設是第\(n\)層節點),進行01揹包後得出第\(n-1\)層節點的結果,再對第\(n-1\)層節點進行01揹包,得出第\(n-2\)層節點的結果……最後答案彙總至超級源點。
注意,超級源點是必選且是額外選的,對於題目給定的揹包容量\(m\)
m++
。最後答案為\(dp[0][m]\)(\(m\)已被擴大過)。
演算法執行的具體順序如圖所示,藍色為未處理節點,綠色為已計算出子樹最優解的節點,橙色為DFS過程中經過的、正在處理的節點。
葉子節點在輸入時就已完成\(dp\)計算(直接用輸入值作為葉子節點的\(dp\)結果)。
int dp[maxn][maxn]; int num = 0; int head[maxn]; int n, m; struct Edge { int next, to; }edges[maxn]; void add_edge(int from, int to) { num++; edges[num].next = head[from]; edges[num].to = to; head[from] = num; } //鏈式前向星存樹 void dfs(int u) { for (int i = head[u]; i; i = edges[i].next) dfs(edges[i].to); //樹的後序遍歷(dfs序) for (int i = head[u]; i; i = edges[i].next) { int v = edges[i].to; //列舉結點u的所有兒子結點,進行O1揹包 for (int j = m; j > 0; j--) { //01揹包的空間壓縮:容量倒序列舉 for (int k = 0; k < j; k++) { dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[v][k]); //dp[u][j]←在dp[u][j-k]的基礎上,再選擇根節點為v的子樹在揹包容量為k時的最優解 } } } } int main() { //ios::sync_with_stdio(false); //while (scanf("%d%d",&n,&m)!=EOF){ //dp[u][j]表示選擇範圍是以u為根節點的整棵子樹、揹包容量為j時的最大價值 cin >> n >> m; //設一個超級源點,就能將森林組織成一棵樹 //而本題輸入中已經自帶了一個無先修課的節點0,可以作為超級源點 m++; //因為必須多選一個超級源點,給它添一個位置 for (int v = 1; v <= n; v++) { int u; cin >> u >> dp[v][1]; //顯然當只有一門課可以選時,選這門課就是揹包容量為1時的最優解 add_edge(u, v); } dfs(0); cout << dp[0][m]; return 0; }