描述:
给定一个有n个节点带边权的树,在S点放置K个机器人,问让机器人遍历所有边的最小花费。
分析:
这题简直把我智商给藐视了,一开始我思路完全错了QAQ。。。被卡了半下午
这题难点在于处理机器人进入子树然后回溯的情况,实际上回溯的过程是有一些性质可循的。如果最终状态的子树中有机器人,那么是没有必要再让其他的机器人进入子树再离开子树。我们完全可以让子树中的机器人走一遍准备进入子树再离开的机器人的路径(进入再回到i点),然后从i点继续走自己原本的路径。这样可以省去进出子树的那条边的花费。只有当子树中最终没有机器人,我们才需要回溯。这种情况下会把子树中的每条边都走两遍,只需要一个机器人去回溯就可以了,这样可以最小化进入子树的花费。
我们令dp[i][j]表示以i为根节点的子树中,最终放j个机器人所对应的最小花费。当j=0,则:dp[i][j]=2*子树内总边权。我们把机器人的数目看做容量,花费看做代价,每个子树要么是dp[i][0]+2*w(w为进入子树的那条边的边权,2表示一进一出,容量为0),要么是dp[i][j]+j*w(j个机器人,只进不出,容量为j)。这就是一个分组(每个子树就是一个组)的01背包问题。
代码:
#include<cstdio>
#include<cstring>
using namespace std;
#define N 10005
#define min(x,y) (x<y?x:y)
struct Edge{
int to,w,next;
};
Edge edge[N<<1];
int dp[N][15],head[N],cnt,S,K,n;
void addedge(int u,int v,int w){
edge[cnt].to=v;edge[cnt].w=w;
edge[cnt].next=head[u];head[u]=cnt++;
}
void dfs(int u,int fa){
int v;
for(int i=0;i<=K;++i) dp[u][i]=0;
for(int i=head[u];i!=-1;i=edge[i].next){
if((v=edge[i].to)==fa) continue;dfs(v,u);
for(int j=K;j>0;--j){
dp[u][j]+=(dp[v][0]+2*edge[i].w);
for(int l=1;l<=j;++l)
dp[u][j]=min(dp[u][j],dp[u][j-l]+dp[v][l]+l*edge[i].w);
}
dp[u][0]+=(dp[v][0]+2*edge[i].w);
}
}
int main(){
int u,v,w;
while(scanf("%d%d%d",&n,&S,&K)!=EOF){
cnt=0;memset(head,-1,sizeof(head));
for(int i=1;i<n;++i){
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);addedge(v,u,w);
}
dfs(S,0);
printf("%d\n",dp[S][K]);
}
return 0;
}