这是IOI2005的原题。
首先打眼一看,这个肯定是树形动规嘛(至少我是这么认为的)。首先想的是用F[i][j]表示i为根的子树里建j个的最小费用。但是转移不动= =
于是就增加一维,这一维是距离i最近的建造了伐木场的祖先。同时,我们使用转换成二叉树和记忆化搜索的办法,可以减少编程复杂度和无用状态。
转移是F[i][j][v]=min(F[lch[i]][p][v]+F[rch[i]][q][v]+dis(i,v)//不在i建p+q=j,F[lch[i]][p][i]+F[rch[i]][q][v]//在i建p+q+1=j)(
//By YY_More
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct treetype{
int son;
int brother;
treetype(){son=-1;brother=-1;}
};
treetype tree[101];
int w[101],d[101],F[101][51][101],dis[101],H[102],n,k,v;
void dfs(int x){
int p=tree[x].son;
while (p!=-1){
dis[p]=dis[x]+d[p];
dfs(p);
p=tree[p].brother;
}
}
void make(int x){
H[x]=1;
if (tree[x].son!=-1){
make(tree[x].son);
H[x]+=H[tree[x].son];
}
if (tree[x].brother!=-1){
make(tree[x].brother);
H[x]+=H[tree[x].brother];
}
}
int dp(int x,int num,int father){
if (F[x][num][father]>-1) return F[x][num][father];
if (tree[x].son==-1&&tree[x].brother==-1)
if (num==0) return w[x]*(dis[x]-dis[father]);
else return 0;
int temp=0x7fffffff;
if (tree[x].son==-1){
if (num>0&&num-1<=H[tree[x].brother]) temp=min(temp,dp(tree[x].brother,num-1,father));
if (num<=H[tree[x].brother]) temp=min(dp(tree[x].brother,num,father)+w[x]*(dis[x]-dis[father]),temp);
F[x][num][father]=temp;
return temp;
}
if (tree[x].brother==-1){
if (num>0&&num-1<=H[tree[x].son]) temp=min(temp,dp(tree[x].son,num-1,x));
if (num<=H[tree[x].son]) temp=min(temp,dp(tree[x].son,num,father)+w[x]*(dis[x]-dis[father]));
F[x][num][father]=temp;
return temp;
}
for (int i=min(num,H[tree[x].son]);i>=0;i--){
if (num-i<=H[tree[x].brother])
temp=min(temp,dp(tree[x].son,i,father)+dp(tree[x].brother,num-i,father)+w[x]*(dis[x]-dis[father]));
if (num-i-1>=0&&num-i-1<=H[tree[x].brother])
temp=min(temp,dp(tree[x].son,i,x)+dp(tree[x].brother,num-i-1,father));
}
F[x][num][father]=temp;
return temp;
}
int main(){
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++){
scanf("%d%d%d",&w[i],&v,&d[i]);
tree[i].brother=tree[v].son;
tree[v].son=i;
}
memset(F,-1,sizeof(F));
dfs(0);
make(0);
printf("%d\n",dp(0,k,0));
return 0;
}