题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4003
题意:给定一棵n个节点的树,遍历每条数边都需要费用cost,现在给定k个机器人,要求用这个k个机器人遍历整棵树,使得经过的费用和最小,n<=10000.
分析:dp[u][j]表示有j个机器人不回来的最小值,dp[u][0]表示有一个机器人回来的最小值,即没有一个机器停留在那颗子树上。至于为甚么只考虑一个机器人回来的原因是同时派多个机器人下去,如果回来的人越多,走重复路线会越多,耗费越多。
这里的树形dp和以往有点不同,原本树形dp对于每个根节点u的儿子v相当于分组背包里的一组,对于每组里的物品(v的儿子)至多取一个进行dp,这里因为要遍历完所有边,所以对于每组的物品必须取一个。
总而言之,假设根节点u有x个子节点,dp[u][j]=min(dp[v1][num1]+dp[v2][num2]+...+dp[vx][numx])
如何分配numk(0<=numk<=j)让num1+num2+...+numx=j使得dp[u][j]值最小。这里对于每个子节点numk枚举0~j枚举一遍就好。
#include <cstdio> #include <cstring> #include <cmath> #include <iostream> #include <algorithm> #include <queue> #include <cstdlib> #include <stack> #include <vector> #include <set> #include <map> #define LL long long #define mod 1000000007 #define inf 0x3f3f3f3f #define N 10010 #define FILL(a,b) (memset(a,b,sizeof(a))) using namespace std; struct edge { int v,w,next; edge(){} edge(int v,int w,int next):v(v),w(w),next(next){} }e[2*N]; int head[N],tot,n,m,s; int dp[N][15]; void addedge(int u,int v,int w) { e[tot]=edge(v,w,head[u]); head[u]=tot++; } void dfs(int u,int fa) { for(int i=head[u];~i;i=e[i].next) { int v=e[i].v,w=e[i].w; if(v==fa)continue; dfs(v,u); for(int j=m;j>=1;j--) { dp[u][j]+=dp[v][0]+2*w;//派一个人下去遍历完后再回来,保证选了一个 for(int k=1;k<=j;k++)//枚举派多个下去,选出最优值 dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]+w*k); } dp[u][0]+=dp[v][0]+2*w; } } int main() { int u,v,w,sum; while(scanf("%d%d%d",&n,&s,&m)>0) { FILL(head,-1);FILL(dp,0);tot=0; for(int i=1;i<n;i++) { scanf("%d%d%d",&u,&v,&w); addedge(u,v,w); addedge(v,u,w); } dfs(s,-1); printf("%d ",dp[s][m]); } }