树上背包问题
樹上背包問題
- 樹上背包問題
- 算法原理
- 例題一:有依賴的背包問題
- 題意
- 數據范圍
- 思路
- 代碼
- 例題二:二叉蘋果樹
- 題意
- 數據范圍
- 思路
- 代碼
- 例題三:Factories(2018icpc銀川網絡賽)
- 題意
- 數據范圍
- 思路
- 代碼
樹上背包問題
一些題目給定了樹形結構,在這個樹形結構中選取一定數量的點或邊(也可能是其他屬性),使得某種與點權或者邊權相關的花費最大或者最小。解決這類問題,一般要考慮使用樹上背包。
算法原理
樹上背包,顧名思義,就是在樹上做背包問題。一個節點的若干子樹可以看作是若干組背包,也就是用樹形dp的方式做分組背包問題。一般來說,f(i,j)f(i,j)f(i,j)表示以iii為根的子樹中,在jjj的容量范圍內,最大或者最小可以獲得多少收益。根據分組背包的思想,第一維枚舉物品(在樹上指的是子樹),第二維枚舉容量,第三維枚舉決策(這里指的是給子樹分配多少容量)?;镜拇a框架如下:
void dfs(int u, int fa) {for(int i = h[u]; ~i; i = ne[i]){int son = e[i];if(son == fa) continue;dfs(son, u);for(int j = m; j >= 0; j --)for(int k = 0; k <= j; k ++)f[u][j] = max(f[u][j], f[u][j-k] + f[son][k] + val);} }例題一:有依賴的背包問題
題意
有nnn個物品和一個容量是mmm的背包。物品之間具有依賴關系,且依賴關系組成一棵樹的形狀。如果選擇一個物品,則必須選擇它的父節點。
求解將哪些物品裝入背包,可使物品總體積不超過背包容量,且總價值最大。輸出最大價值。
每件物品的編號是iii,體積是viv_ivi?,價值是wiw_iwi?,依賴的父節點編號是pip_ipi?。物品的下標范圍是1…N1 \dots N1…N。
數據范圍
1≤n,m≤1001 \leq n,m \leq 1001≤n,m≤100
1≤vi,wi≤1001 \leq v_i,w_i \leq 1001≤vi?,wi?≤100
思路
f(i,j)f(i,j)f(i,j)表示選擇以iii為子樹的物品,在容量不超過jjj時所獲得的最大價值。
由于只有選擇了根節點,才會繼續往下遍歷,所以在遍歷到iii節點時,先考慮一定選上它。
在分組背包部分,jjj的范圍為[m,v[i]][m,v[i]][m,v[i]],否則沒有意義,因為連根節點也放不下;kkk的范圍[0,j?v[i]][0,j-v[i]][0,j?v[i]],當大于j?v[i]j-v[i]j?v[i]時分給該子樹的容量過多,剩余的容量連根節點的物品都放不下了。
遞推式為:f(i,j)=max(f(i,j),f(i,j?k)+f(son,k))f(i,j) = max(f(i,j), f(i,j - k) + f(son,k))f(i,j)=max(f(i,j),f(i,j?k)+f(son,k))。
代碼
void dfs(int u) {for(int i = v[u]; i <= m; i ++) f[u][i] = w[u];for(int i = h[u]; ~i; i = ne[i]){int son = e[i];dfs(son);for(int j = m; j >= v[u]; j --)for(int k = 0; k <= j - v[u]; k ++)f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);} }例題二:二叉蘋果樹
題意
給定一棵二叉樹,每條邊有邊權,保留一定數量的邊(其他邊刪除),使得保留下來的邊的邊權和最大。
數據范圍
1≤n<m≤1001 \leq n < m \leq 1001≤n<m≤100
wi≤30000w_i \leq 30000wi?≤30000
思路
f(i,j)f(i,j)f(i,j)表示以iii為根的子樹中,恰好保留jjj條邊的最大邊權和。
若需要選擇該子樹中的邊,則根結點到子樹的邊一定要選,因此能用上的總邊數一定減111,總共可以選擇jjj條邊時,當前子樹son分配的最大邊數是j?1j - 1j?1。
遞推式為,f(i,j)=max(f(i,j),f(i,j?k?1)+f(son,k)+w[i])f(i,j) = max(f(i,j), f(i,j-k-1) + f(son, k) + w[i])f(i,j)=max(f(i,j),f(i,j?k?1)+f(son,k)+w[i])。
代碼
void dfs(int u, int fa) {for(int i = h[u]; ~i; i = ne[i]){int son = e[i];if(son == fa) continue;dfs(son, u);for(int j = m; j >= 1; j -- )for(int k = 0; k <= j - 1; k ++ )f[u][j] = max(f[u][j], f[u][j - k - 1] + f[son][k] + w[i]);} }例題三:Factories(2018icpc銀川網絡賽)
題意
給定一棵樹,邊有邊權。每個葉子節點上最多可以布置一個工廠,總共要布置kkk個工廠。問怎樣布置工廠,使得工廠之間的距離和最小。
數據范圍
10s10s10s
2≤n≤1052 \leq n \leq 10^52≤n≤105, 1≤m≤1001 \leq m \leq 1001≤m≤100
1≤wi≤1051 \leq w_i \leq 10^51≤wi?≤105
多組測試數據,nnn總數不超過10610^6106
思路
直接考慮距離之和非常困難,所以可以考慮每條邊被計算了幾次(距離和等類似問題很多都是這么考慮的)。不妨設一條邊為iii,與iii相連的子樹中有jjj個工廠,則這條邊被計算的次數為j?(m?j)j*(m - j)j?(m?j)。
f(i,j)f(i,j)f(i,j)表示以iii為根節點的子樹中,選擇恰好jjj個葉子節點的距離總和。
遞推式為,f(i,j)=min(f(i,j),f(i,j?k)+f(son,k)+w[i]?j?(m?j))f(i,j) = min(f(i,j), f(i,j - k) + f(son, k) + w[i] * j * (m - j))f(i,j)=min(f(i,j),f(i,j?k)+f(son,k)+w[i]?j?(m?j))。
因為只能分布在葉子節點,因此初始化的時候要注意,如果點iii為葉子節點,那么f(i,1)=0f(i,1) = 0f(i,1)=0。
同時這道題要卡常數,所以要對狀態做一個優化,即把無效狀態去掉。
代碼
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm>using namespace std;typedef long long ll;const int N = 100003, M = 103; const ll inf = 1e18;int n, m; int h[N], e[2*N], ne[2*N], w[2*N], idx; int s[N], deg[N]; ll f[N][M];void add(int a,int b,int c) {e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++; }void dfs(int u,int fa) {for(int i = h[u]; ~i; i = ne[i]){int son = e[i];if(son == fa) continue;dfs(son, u);s[u] += s[son];for(int j = min(m, s[u]); j >= 1; j --)for(int k = 1; k <= min(j, s[son]); k ++)f[u][j] = min(f[u][j], f[u][j-k] + f[son][k] + (ll)w[i] * k * (m - k));} }int main() {int T;scanf("%d", &T);int cas = 0;while(T --){scanf("%d%d", &n,&m);for(int i = 1; i <= n; i ++) h[i] = -1, deg[i] = 0;idx = 0;for(int i = 0; i < n - 1; i ++){int a,b,c;scanf("%d%d%d", &a,&b,&c);add(a,b,c), add(b,a,c);deg[a] ++, deg[b] ++;}for(int i = 1; i <= n; i ++){s[i] = 0;for(int j = 1; j <= m; j ++) f[i][j] = inf;if(deg[i]==1) f[i][1] = 0, s[i] = 1;}dfs(1, -1);printf("Case #%d: %lld\n",++cas,f[1][m]);}return 0; }總結
- 上一篇: 存货账龄分析报表(上)
- 下一篇: MySQL性能管理及架构设计(一):什么