P1273 有线电视网 树形DP 树上背包
题意
给定一颗有根树,要求选择最多的叶子结点,并且使得达到这些叶子结点的花费(可以理解成到叶子的路径权值和) 小于等于选择的叶子结点的点权和。
[2leq nleq 3000\
1leq m leq n - 1
]
分析
经典的树上背包问题。
可以对一些叶子结点进行选或者不选的决策
以下参考洛谷 yqw2486的题解
状态转移方程
[dp[i][u][j] = max(dp[i - 1][u][j - k] + dp[v的总儿子数][v][k] - w(u,v))
]
(dp[i][u][j])表示以(u)为根的子树的前(i)棵子树中选择(j)个节点能够获得的最大费用
边界
[dp[u][1] = w[u]\
dp[u][0] = 0
]
显然我们可以类似01背包的办法滚动一维
[dp[u][j] = max(dp[u][j-k] + dp[v][k] - w(u,v))
]
代码
struct Edge {
int v;
int w;
Edge(int _v, int _w) {
v = _v;
w = _w;
}
};
vector<Edge> e[3005];
int w[3005];
int dp[3005][3005];
int n, m;
int dfs(int u) {
if (u > n - m) {
dp[u][1] = w[u];
dp[u][0] = 0;
return 1;
}
dp[u][0] = 0;
int siz = 0;
for (auto v : e[u]) {
int val = v.w;
int vv = v.v;
int t = dfs(vv);
siz += t;
for(int j = siz;j > 0;j--)
for (int k = 0; k <= min(j, t); k++)
dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[vv][k] - val);
}
return siz;
}
int main() {
n = readint();
m = readint();
for (int i = 1; i <= n - m; i++) {
int k = readint();
for (int j = 1; j <= k; j++) {
int x = readint();
int y = readint();
e[i].push_back(Edge(x, y));
}
}
for (int i = n - m + 1; i <= n; i++)
w[i] = readint();
for (int i = 0; i < 3005; i++)
for (int j = 0; j < 3005; j++)
dp[i][j] = -1e9;
for (int i = 0; i < 3005; i++)
dp[0][i] = 0;
dfs(1);
for (int i = m; i >= 0; i--)
if (dp[1][i] >= 0) {
printf("%d", i);
break;
}
}