题意:给出结点数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]放进背包,如果该组里有更好的选择,那么就会换掉这个物品,否则的话这个物品就是最好的选择。这样保证每组必定选了一个。
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65768/65768 K (Java/Others) Total Submission(s): 2198 Accepted Submission(s): 1001
#include <stdio.h> #include<iostream> #include <string.h> #include <algorithm> using namespace std; struct node { int now,next,val; }tree[20005]; int len,n,s,k; int head[10005],dp[10005][15],vis[10005]; void add(int x,int y,int v) { tree[len].now=y; tree[len].val=v; tree[len].next=head[x]; head[x]=len++; } void dfs(int root,int p) { int i,j,q,son; vis[root]=1; for(i=head[root];i!=-1;i=tree[i].next) { son=tree[i].now; if(son==p) continue; if(!vis[son]) { 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(q=1;q<=j;q++)//在这里找到比dp[son][0]更优的方案,分组背包 dp[root][j]=min(dp[root][j],dp[root][j-q]+dp[son][q]+q*tree[i].val); //必须是乘以p,p以前的价值要加上。 } } } } int main() { int i,x,y,w; while(~scanf("%d%d%d",&n,&s,&k)) { memset(dp,0,sizeof(dp)); memset(head,-1,sizeof(head)); memset(vis,0,sizeof(vis)); len=0; 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; }