Description
n
≤
100
,
k
≤
m
i
n
(
n
,
50
)
n≤100,k≤min(n,50)
n≤100,k≤min(n,50).
Solution
考虑树形 d p dp dp。
状态设计为 d p i , j dp_{i,j} dpi,j表示在 i i i子树中建立了 j j j个伐木场的最少花费。
考虑状态转移。直接转移看起来十分困难,于是我们采用费用提前计算的 d p dp dp技巧。即,对于每个节点都有一个贡献;具体的,第 i i i个节点的贡献为 w i × d i s ( i , p ) w_i×dis(i,p) wi×dis(i,p),这里 p p p表示离 i i i最近的伐木场。
但是我们并不知道离 i i i最近的有伐木场的祖先节点在哪里……所以无法进行状态转移。
根据上面的尝试,我们考虑再加一维。 d p i , j , k dp_{i,j,k} dpi,j,k表示在 i i i子树中建立了 k k k个伐木场,且离 i i i最近的有伐木场的祖先为 j j j时的最少花费。
不难发现这是一个经典的背包模型。假设我们想要求出 d p n o w dp_{now} dpnow的所有状态。每次将 n o w now now已有的状态与孩子节点的状态进行合并,最后再对于每一个形如 d p n o w , p , k dp_{now,p,k} dpnow,p,k的状态加上一个提前计算的代价 ( p r e n o w − p r e p ) × a n o w (pre_{now}-pre_{p})×a_{now} (prenow−prep)×anow即可。
树形背包是 O ( n 2 ) O(n^2) O(n2)的,外加了一个枚举离 i i i最近的有伐木场的祖先 p p p的代价,所以总时间复杂度为 O ( n 3 ) O(n^3) O(n3)。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxl=102,maxk=52;
int n,K,cnt=0,u,v,pos=0;
int head[maxl],a[maxl],s[maxl],f[maxl][maxl][maxk],g[maxl][maxl][maxk],pre[maxl];
struct edge{int nxt,to,dis;}e[2*maxl];
void add_edge(int u,int v,int w){
cnt++;
e[cnt].to=v,e[cnt].nxt=head[u],e[cnt].dis=w;head[u]=cnt;
}
void dfs(int now,int fath,int up){
pre[now]=pre[fath]+up;
s[++pos]=now;
for (int i=head[now];i;i=e[i].nxt){
int y=e[i].to;
if (y==fath) continue;
dfs(y,now,e[i].dis);
for (int j=1;j<=pos;j++){
int tmp=s[j];
for (int k=K;k>=0;k--){
f[now][tmp][k]+=f[y][tmp][0];
g[now][tmp][k]+=f[y][now][0];
for (int x=0;x<=k;x++){
f[now][tmp][k]=min(f[now][tmp][k],f[now][tmp][x]+f[y][tmp][k-x]);
g[now][tmp][k]=min(g[now][tmp][k],g[now][tmp][x]+f[y][now][k-x]);
}
}
}
}
for (int i=1;i<=pos;i++){
int tmp=s[i];
for (int j=0;j<=K;j++){
f[now][tmp][j]=f[now][tmp][j]+a[now]*(pre[now]-pre[tmp]);
if (j>=1) f[now][tmp][j]=min(f[now][tmp][j],g[now][tmp][j-1]);
}
}
pos--;
}
signed main(){
cin>>n>>K;
for (int i=1;i<=n;i++){
cin>>a[i]>>u>>v;
add_edge(i,u,v),add_edge(u,i,v);
}
dfs(0,-1,0);
cout<<f[0][0][K]<<endl;
return 0;
}