3 1 1 1 2 1 1 3 1 3 1 2 1 2 1 1 3 1
3 2HintIn the first case: 1->2->1->3 the cost is 3; In the second case: 1->2; 1->3 the cost is 2;
题意:给出结点数n,起点s,机器人数k,然后n-1行给出相互连接的两个点,还有这条路线的价值,要求最小花费
思路:这是我从别人博客里找到的解释,因为很详细就引用了
dp[i][j]表示对于以i结点为根结点的子树,放j个机器人所需要的权值和。
当j=0时表示放了一个机器人下去,遍历完结点后又回到i结点了。状态转移方程类似背包
如果最终的状态中以i为根结点的树中有j(j>0)个机器人,那么不可能有别的机器人r到了这棵树后又跑到别的树中去
因为那样的话,一定会比j中的某一个到达i后跑与r相同的路径再回到i,再接着跑它的路径要差(多了一条i回去的边)
这样的话,如果最后以i为根结点的树中没有机器人,那么只可能是派一个机器人下去遍历完后再回来
可以这么理解:
对于每个根节点root,有个容量为K的背包
如果它有i个儿子,那么就有i组物品,价值分别为dp[son][0],dp[son][1].....dp[son][k] ,这些物品的重量分别为0,1,.....k
现在要求从每组里选一个物品(且必须选一个物品)装进root的背包,使得容量不超过k的情况下价值最大。
那么这就是个分组背包的问题了。
但是这里有一个问题,就是每组必须选一个物品。
对于这个的处理,我们先将dp[son][0]放进背包,如果该组里有更好的选择,那么就会换掉这个物品,否则的话这个物品就是最好的选择。这样保证每组必定选了一个。
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; struct Node { int now,next,val; }tree[20005]; int dp[10005][15]; int head[10005]; int n,s,k,len; void add(int x,int y,int w)//建树 { tree[len].now = y; tree[len].val = w; tree[len].next = head[x]; head[x] = len++; } void dfs(int root,int p) { int i,j,l,son; for(i = head[root];i!=-1;i = tree[i].next) { son = tree[i].now; if(son == p) continue; dfs(son,root); for(j = k;j>=0;j--) { dp[root][j]+=dp[son][0]+2*tree[i].val;//先将dp[son][0]放进背包,由于dp[son][0]是表示用一个机器人去走完所有子树,最后又回到pos这个节点,所以花费要乘以2 for(l = 1;l<=j;l++)//在这里找到比dp[son][0]更优的方案,分组背包 dp[root][j] = min(dp[root][j],dp[root][j-l]+dp[son][l]+l*tree[i].val); } } } int main() { int i,x,y,w; while(~scanf("%d%d%d",&n,&s,&k)) { len = 0; memset(head,-1,sizeof(head)); memset(dp,0,sizeof(dp)); for(i = 1;i<n;i++) { scanf("%d%d%d",&x,&y,&w); add(x,y,w); add(y,x,w); } dfs(s,-1); printf("%d\n",dp[s][k]); } return 0; }