题意: 给你一颗N个节点的树,和K个机器人从S点出发,问走遍所有点的最小路程是?机器人可以走回头路。
思路:这题情形是:如果树某一个节点的分支数>k,那么必然会有从根节点派往子节点并且折回到根节点,就是会走重复的边,就像第一个案例。
设 dp[i][j] : 表示在i节点有派了j个机器人的最小路程耗费,定义DP[i][0]存放1个机器人,从子节点返回到根节点的花费。
思考,(1)如果只有一个机器人,那么只有一种方法,就是用这一个机器人跑遍每个子节点V、也就是每次机器人都要从一个子节点返回根节点,然后走向另一个节点,直到遍历所有点位止。那么这种状态下的状态转移为:dp[u][j]+=dp[v][0]+w*2;(u是当前结点,v是u的子节点,w是u,v之间边的权值)。
(2)当机器人的个数>1时,当前的决策就是派多少机器人去遍历剩下的子节点。思考发现这就是一个很典型的分组背包问题。
以下这段摘自别人的分析:
可以这么理解:
对于每个根节点root,有个容量为K的背包
如果它有i个儿子,那么就有i组物品,价值分别为dp[son][0],dp[son][1].....dp[son][k] ,这些物品的重量分别为0,1,.....k
现在要求从每组里选一个物品(且必须选一个物品)装进root的背包,使得容量不超过k的情况下价值最大。
那么这就是个分组背包的问题了。
但是这里有一个问题,就是每组必须选一个物品。
对于这个的处理,我们先将dp[son][0]放进背包,如果该组里有更好的选择,那么就会换掉这个物品,否则的话这个物品就是最好的选择。这样保证每组必定选了一个。
那么就可以得到
dp[root][j]=min(dp[root][j-k]+dp[son][k]+k*w);的转移方程,相当于选择当前背包大小为k的背包。#include<bits/stdc++.h>
using namespace std;
template<int N,int M>//N点的个数,M边的个数
struct Graph{
int top;
struct Vertex{
int head;
}V[N];
struct Edge{
int v,next,w;
}E[M];
void init(){
memset(V,-1,sizeof(V));
top = 0;
}
void add_edge(int u,int v,int w){
E[top].v = v;
E[top].next = V[u].head;
E[top].w=w;
V[u].head = top++;
}
};
const int maxn=10000+10;
Graph<maxn,maxn*2> g;
int N,S,K,dp[maxn][15];
void dfs(int u,int f){
for(int i=g.V[u].head;i!=-1;i=g.E[i].next){
int v=g.E[i].v,w=g.E[i].w;
if(v==f) continue;
dfs(v,u);
for(int j=K;j>=0;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]+k*w);
}
}
}
}
int main(){
while(~scanf("%d%d%d",&N,&S,&K)){
g.init();
for(int i=0;i<N-1;i++){
int a,b,c;scanf("%d%d%d",&a,&b,&c);
g.add_edge(a,b,c);
g.add_edge(b,a,c);
}
memset(dp,0,sizeof(dp));
dfs(S,-1);
printf("%d\n",dp[S][K]);
}
}
题外话:改编一下,如果规定出发点不限,应该怎么做呢?(我还没想到..)如果规定机器人只有一个,并且出发点不限,那就变成hdu4607。