給定一棵有$n$個節點的點權樹,要求你從中選出$m$個節點,使得這些選出的節點的點權和最大,乙個節點能被選當且僅當其父親節點被選中,根節點可以直接選。
考慮設$f[u][i]$表示在$u$的子樹中選擇$i$個節點(包括它本身)的最大貢獻,則可列出以下轉移方程。
$$f[u][i]=max(f[u][j]+f[v][i-j]+d[v])\ [j=1...i-1]
$$其中$d[v]$表示點$v$的點權,$i-j$表示在子樹$v$中選擇$i-j$個節點。
由於遍歷整棵樹是$\theta(n)$的,而選取$i$和$j$是$o(m2)$的,所以整個程式的複雜度就是$o(nm2)$的。
luogu p2014 選課
這是一道樹形揹包的模板題,可以將題目轉化為在$n+1$個節點中選$m+1$個節點。於是最後的答案就是$f[0][m+1]$。
#include #include using std::max;
const int n = 3e2 + 10, m = 3e2 + 10;
int n, m, f[n][n], s[n], son[n][n];
void dfs (int u)
}int main ()
dfs(0);
printf ("%d\n", f[0][m + 1]);
return 0;
}
顯然,$n^3$演算法的時間開銷是很$big$的,比如這道題:洛谷 p4322 最佳團體。
此題在$01$分數規劃後採取樹形揹包$check$,但是,$nm^2log$的時間複雜度是不允許,考慮優化樹形揹包的$check$過程
首先,既然要優化,我們就得知道瓶頸在哪。瓶頸在於,我們是一邊$dfs$一邊更新的,由於要遍歷子樹,我們同時還要知道選擇多少個節點,那麼我們是否可以先跑一遍$dfs$處理出$dfs$序然後根據$dfs$序,來更新。
設$f[i][j]$為當前$dp$到$dfs$序為$i$的點,目前已經選了$j$個節點。則有轉移方程($d[i]$表示點權):
1.選取當前節點:$$
f[i+1][j+1]=f[i][j]+d[i]
$$如果選了這個點,則在$dfs$序後乙個節點要麼是它的子節點,要麼下一棵子樹(則證明其沒有子節點)。
2.不選當前節點:$$
f[nx[i]][j]=f[i][j]
$$其中$nx[i]$表示下一棵子樹,因為你沒選這個點,當然不能選擇其子節點。
由於$dfs$序為$\theta(n)$的,然後列舉$j$為$o(m)$的,所以總複雜度為$o(nmlog)$。
同樣是luogu p2014 選課
#include #include using std::min;
typedef long long ll;
const int n = 3e2 + 10, m = 3e2 + 10, inf = 1e9 + 7;
int n, m, d[n], s[n], dfn[n], son[n][n], time, f[n][n], nx[n];
inline void upt (int &a, int b)
void init_dfs(int u)
void doit_dp()
}int main ()
init_dfs(0);//預處理dfs
doit_dp();//動態規劃
printf("%d\n", f[n + 1][m]);
return 0;
}
之前我們提到的洛谷 p4322 最佳團體,就是用$01$分數規劃&樹形揹包來解決的
// luogu-judger-enable-o2
#include #include using std::min;
using std::max;
const int n = 3e3 + 10, inf = 1e9 + 7;
const double eps = 1e-5;
int n, k, s[n], p[n], son[n][n], dfn[n], time, nx[n];
int from[n], to[n], nxt[n], cnt;//edges
double f[n][n], d[n];
inline void addedge (int u, int v)
inline void upt(double &a, double b)
void dfs (int u)
inline bool check (double k)
return f[n + 1][k] >= eps;
}int main ()
dfs(0);
double l = 0, r = 10000, ans;
while (r - l >= eps)
printf ("%.3lf\n", ans);
return 0;
}
樹形依賴揹包
問題大意 給出一棵樹,根節點為1,每個點有毒素和收穫。要求毒素不超過給定值的情況下使收穫最大。乙個點的父親節點被選取後這個點才能被選取。首先弄出dfs序,也記錄下每個點其子樹及自身的大小。每個點都能夠被選或不選,如果選了才會考慮它子樹。設f i j 表示dfs序上第i位上的點在其子樹及自身上選取了毒...
樹形揹包總結
目錄 2 有物品大小 3 物品大小為1,有k的限制。二 dfs序上dp 例題1例題2 例題3總結下 樹形揹包,就是說,在樹上選乙個包含根的連通塊,或揹包存在依賴關係 選父才能選子 或者需要知道每個點的子樹中選了多少 通常,我們有兩種方法 我們設 dp i,j 表示在i的子節點中選j個的狀態。在轉移時...
樹形揹包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 ...