题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4003
题意:给定一棵n个结点的树及树根,树的每条路径有一个权值,现在树根上有k个机器人,用这k个机器人遍历这棵树,求机器人走的权值之和的最小值。
树形dp的题目还是做的太少了。
我们用f[i][j]表示在以i为根的子树里有j个机器人遍历这棵子树权值之和的最小值,特别的,我们用f[i][0]表示用一个机器人走完以i为根的子树并且这个机器人又回到i所走的权值和。
那么,显然我们可以得到f[i][0]=∑(f[each son of i ][0]+2*weight[i][each son of i])
假设结点x有i个儿子,那么对于每一个儿子i有f[i][0],f[i][1]……f[i][k]共k+1种状态,那么我们可以把每个儿子分成一组,i个儿子即为i组,对于每一组含有k+个有价值的物品,这样我们就把它近似的抽象成了一个分组背包的问题,也就是说,f[x][j]是由x的每个儿子中选择一个物品更新而来的。
这里和分组背包的不同点在于:分组背包要求每组物品至多选择一个,而这道题要求每一组必须选择一个物品。所以我们开始的时候将f[x][j]初始化为f[son][0]+2*weight[x][son],然后找到比当前f[x][j]值小的解时更新,这样可以保证一定会选择一组。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
struct node
{
int x,w,next;
};
int len;
int n,s,k;
node tree[20005];
int head[10005];
int f[10005][13];
void add(int a,int b, int c)
{
tree[len].x=b; tree[len].w=c;
tree[len].next=head[a];
head[a]=len; len++;
}
void dfs(int s,int p)
{
for (int i=head[s];i!=-1;i=tree[i].next)
{
int x=tree[i].x;
if (x==p) continue;
dfs(x,s);
for (int j=k;j>=0;j--)
{
f[s][j]+=f[x][0]+2*tree[i].w;
for (int l=1;l<=j;l++)
f[s][j]=min(f[s][j],f[s][j-l]+f[x][l]+l*tree[i].w);
}
}
}
int main()
{
while (scanf("%d%d%d",&n,&s,&k)!=EOF)
{
memset(head,-1,sizeof(head));
memset(f,0,sizeof(f));
len=0;
for (int i=0;i<n-1;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c); add(b,a,c);
}
dfs(s,-1);
printf("%d\n",f[s][k]);
}
return 0;
}