题意
输入描述
There are multiple cases in the input.
In each case:
The first line specifies three integers N, S, K specifying the numbers of metal mineral, landing site and the number of robots.
The next n‐1 lines will give three integers x, y, w in each line specifying there is a path connected point x and y which should cost w.
1<=N<=10000, 1<=S<=N, 1<=k<=10, 1<=x, y<=N, 1<=w<=10000.
输出描述
For each cases output one line with the minimal energy cost.
题目大意
火星上有一种金属材料,需要去获得,火星的地图可以看做一棵有N个节点的树,其根节点为S,你要派去K个机器人从S出发去遍历所有的节点以获得材料。再告诉你相互连接的点与相应的花费,若机器人可以从任意节点将所收集到的材料带回地球,询问获取到所有材料的最少花费。
题解
思路
首先要明确的是,既然机器人可以从任意节点返回,那么我们就用f[x][i]来表示在以x节点为根节点的子树上花费 i个机器人需要的最少花费。什么叫花费呢,这i个机器人最终将停留在x子树上,也就是说最后这些机器人是从这棵子树上的某个节点返回的。比如f[x][0]表示说没有机器人会待在这棵子树上,它们进去遍历完子节点后都会出来。
那么剩下的问题就好解决了。由于必须要遍历所有节点,那么每个子树都要有机器人去,那么对于子树的状态,选且仅能选一种。那不就是一个树上分组背包嘛,我们可以将子树视作一组,物品即为子树的状态。
代码
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int size=10100;
struct date{
int v,w,nxt;
}edge[size<<1];
int n,s,k,p,ans,head[size],f[size][15];
int min(int x,int y){return x<y?x:y;}
void add(int u,int v,int w)//链式前向星,很好用的储存方法
{
edge[++p].v=v;
edge[p].w=w;
edge[p].nxt=head[u];
head[u]=p;
}
bool input()
{
int u,v,w;
if(scanf("%d",&n)==EOF)
return false;
scanf("%d%d",&s,&k);
p=0;
memset(head,0,sizeof(head));
memset(f,0,sizeof(f));
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);//要建反向边
}
return true;
}
void dp(int x,int pre)
{
for(int i=head[x];i;i=edge[i].nxt)
{
if(edge[i].v==pre)//避免搜到前驱节点
continue;
dp(edge[i].v,x);
for(int j=k;j>=0;j--)
{
f[x][j]+=f[edge[i].v][0]+edge[i].w*2;//因为要回来,所以代价*2
for(int r=1;r<=j;r++)
f[x][j]=min(f[x][j],f[x][j-r]+f[edge[i].v][r]+r*edge[i].w);//r个机器人留在x子树中
}
}
}
int main()
{
while(input())
{
dp(s,-1);
printf("%d\n",f[s][k]);
}
return 0;
}