Find Metal Mineral
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65768/65768 K (Java/Others)
Total Submission(s): 1744 Accepted Submission(s): 774
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.
树形DP+只能选一个物品的分组背包
dp[pos][num]表示以pos为根节点的子树下,用去num个机器人,所得到的最小值
特别的是当num==0的时候,dp[pos][0]表示用一个机器人去走完所有子树,最后又回到pos这个节点
状态转移:dp[pos][num]=min∑{dp[pos_j][num_j]+w_j},pos_j是pos的所有儿子,
为了让分组背包只选一个,首先让dp[u][0]如背包
使用一维数组的“分组背包”伪代码如下:
for 所有的组i
for v=V..0
for 所有的k属于组i
f[v]=max{f[v],f[v-c[k]]+w[k]}
根据题意可以知道,机器人是可以走回头路的。稍微分析下可以知道,如果从根节点派往子节点如果要折回到根节点,派向这个子节点的机器人越多,产生的重复路径就会越多,所以那么派往这个子节点的个数为1时是最优策略。那么定义DP[i][0]存放1个机器人,从子节点返回到根节点的花费。
关键是思考:如果 根节点s, 有 n个子节点,那么要怎么样选择才能使dp[s][k]的值最小呢?
⑴当机器人的个数为1时,那么只有一种方法,就事用这一个机器人跑遍每个子节点、也就是每次机器人都要从一个子节点返回根节点,然后走向另一个节点,直到遍历所有点位止。那么这种状态下的状态转移为: dp[s][0]=dp[next][0]+2*w; (其中s为根节点,next为子节点,w为s到next的权值)。
⑵当机器人的个数>1时,那么必然存在机器人不必返回根节点的策略。同时由⑴也可以求出折回根节点的花费。
那么求根节点s下的n个子节点,把每个节点看做一个整体,定义s(n,j)为前n个节点派j个机器人的最小花费,很容易发现这也是个DP,其实就是个背包,s(n-1,j)与s(n,j)必然存在某种联系。可以推出s(n,j)=min(s(n,j), s(n-1,j-k)+dp[v][k]+k*w); (1<=k<=j,w为s到当前这个子节点v的权值)。因为只要用到上一个子节点的状态,所以对起始点s的所有子节点扫描一遍就可以了。
其实这题的树形DP,就是 DP套DP。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int VM=10010; struct Edge{ int to,nxt; int cap; }edge[VM<<1]; int N,S,K,cnt,head[VM]; int vis[VM],dp[VM][20]; //dp[i][j]表示以i为根的树用掉j个人。dp[i][0]表示用了一个人又回到上面的点 void addedge(int cu,int cv,int cw){ edge[cnt].to=cv; edge[cnt].cap=cw; edge[cnt].nxt=head[cu]; head[cu]=cnt++; } void DFS(int u){ if(vis[u]) return ; vis[u]=1; for(int i=head[u];i!=-1;i=edge[i].nxt){ int v=edge[i].to; if(!vis[v]){ DFS(v); for(int j=K;j>=0;j--){ dp[u][j]+=dp[v][0]+2*edge[i].cap; //先把dp[u][0]放进背包,保证至少选一个 for(int k=1;k<=j;k++) // 分组背包 dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]+k*edge[i].cap); } } } } int main(){ //freopen("input.txt","r",stdin); while(~scanf("%d%d%d",&N,&S,&K)){ cnt=0; memset(head,-1,sizeof(head)); int u,v,w; for(int i=1;i<N;i++){ scanf("%d%d%d",&u,&v,&w); addedge(u,v,w); addedge(v,u,w); } memset(vis,0,sizeof(vis)); memset(dp,0,sizeof(dp)); DFS(S); printf("%d\n",dp[S][K]); } return 0; }