这个题是一道树形DP,非常能开阔眼界。
状态
f[i][j][k] 表示 以i为子树,在j建一个伐木场,i得子树中一共允诺了k个伐木场。
这里得允诺就是一个套路性得DP方法,我们发现依照一开始f[i][j]表示在i得子树中,点集为j没有伐木场这种树形DP好像不可以转移,我们就能考虑到,在转移的过程中有必要知道哪里有伐木场,才能进行转移。
转移得时候依靠背包,因为当前点i无非只要建伐木场和不建伐木场,就能得到转移
f数组表示i没建造了伐木场, g表示i建了造伐木场
f[i][j][k] += f[son[i]][j][0];//i 得子树都可以直接用i曾经允诺过建造伐木场得伐木场
g[i][j][k] += f[son[i]][i][0];
然后进行普通的树形背包转移
接着我们知道,当前得价值就是指定j有伐木场,价值还没有更新,所以需要更新价值,即
其实我们可以看出来g[i][stac[j]][k] 是一个冗余得状态。
他所多记录的信息就是i有没有伐木场,完全可以被f[i][i][k-1]这个状态替代。
所以我们就可以少转移了。
贴个代码
#include <bits/stdc++.h> using namespace std; //转移:枚举字数里有多少个伐木场 typedef long long LL; const int N = 120; const int M = 55; int n, K, dep[N]; int val[N], head[N], cnt; int stac[N], top;//树上枚举一般都要用栈 LL f[N][N][M]; int in1, in2; struct Edge{ int to; int nxt; int wei; }e[N << 1]; void add(int x,int y,int z){ e[++cnt].to = y; e[cnt].wei = z; e[cnt].nxt = head[x]; head[x] = cnt; } void dfs(int i){ stac[++top] = i; for(int t = head[i];t;t = e[t].nxt){ int v = e[t].to; dep[v] = dep[i] + e[t].wei; dfs(v); for(int j = 1;j <= top;++ j){ for(int k = K;k >= 0;-- k){ f[i][stac[j]][k] += f[v][stac[j]][0];//子树都不用建造了 for(int x = 0;x <= k;++ x) f[i][stac[j]][k] = min(f[i][stac[j]][k], f[i][stac[j]][k-x] + f[v][stac[j]][x]); } } } for(int j = 1;j <= top;++ j){ for(int k = 0;k <= K;++ k){ if(k >= 1) f[i][stac[j]][k] = min(f[i][stac[j]][k] + val[i] * (dep[i] - dep[stac[j]]), f[i][i][k-1]); else f[i][stac[j]][k] += val[i] * (dep[i] - dep[stac[j]]); } } top --; } int main(){ scanf("%d %d", &n, &K); for(int i = 1;i <= n;++ i){ scanf("%d %d %d", &val[i], &in1, &in2); add(in1, i, in2); } dfs(0); printf("%d", f[0][0][K]); return 0; }