HDU 4003 Find Metal Mineral (树形DP,经典)

题意:给定一棵树图,n个节点,有边权,要派k<11个机器人从节点s出发,遍历所有的点,每当1只机器人经过1条边时就会花费该边的边权,边是可重复走的。问遍历完所有点的最小花费?

思路:

  非常经典,首先需要知道一点,才能往下推理。就是“如果在t点派c个机器人往孩子u,那么最多只有1个机器人能走会回来到t,否则花费总是不划算的”。

  稍微证明一下:

  (1)假设派1个机器人往u,逛一圈回到u的父亲t,花费v= 子树u的边权和*2 + e(t,u)*2。若机器人不要了,那花费肯定比v还要少。

  (2)假设派2个机器人往u,若要2个机器人都回来,显然花费要比(1)要大。若仅要1个机器人回来,花费仍比“仅派1只机器人往u”要高。(可以试试画一棵有3层的树,头两层只有1个节点,第三层节点数随意即可,来模拟一下就知道结果了)

  得到的结论是,要么只派c个机器人去孩子u,且1个都不要走回来(注:0<c<=k);要么派1个机器人出去,且遍历完子树u走回t。

  这样已经很好解决了。枚举下点t的孩子数(组),再枚举点t可能获得的机器人数(容量),再枚举给这个孩子派的机器人数(物品)。

  还有个初始化DP值的问题,因为每个孩子都是必须遍历的,而常规的分组背包是可选(至多选1件)或者不选物品的,那么可以先将“派1个机器人且回收1个机器人的DP值”丢进背包(保证了此容量下肯定有遍历过子树u),然后若有更好的状态再更新掉此容量。

   用DP[t][0]表示“有1个机器人到达t遍历完子树再走回t”的花费。DP[t][i]表示“有i个机器人到达t且遍历完整棵子树t,且不要求回到t”的最小花费。

 1 #include <bits/stdc++.h>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <map>
 5 #include <vector>
 6 #include <iostream>
 7 #define pii pair<int,int>
 8 #define INF 0x3f3f3f3f
 9 #define LL  long long
10 using namespace std;
11 const int N=1010;
12 struct tower
13 {
14     int pri,pow;
15 }tow[N][60];
16 
17 struct node
18 {
19     int from,to,next;
20     node(){};
21     node(int from,int to,int next):from(from),to(to),next(next){};
22 }edge[N*2];
23 
24 int edge_cnt, head[N], cnt[N], dp[N][210], n, m ;
25 void add_node(int from,int to)
26 {
27     edge[edge_cnt]=node(from, to, head[from]);
28     head[from]=edge_cnt++;
29 }
30 inline int cmp(tower a,tower b){return a.pri<b.pri;}
31 
32 void DFS(int t,int far)
33 {
34     node e;
35     int flag=0;
36     for(int i=head[t]; i!=-1; i=e.next)
37     {
38         e=edge[i];
39         if(e.to==far)  continue;
40         DFS(e.to, t);
41 
42         for(int j=m; j>=0; j-- )  //容量
43         {
44             dp[t][j]=min(dp[t][j], dp[e.to][0]);  //先装代价为0的,如果有更好的再代替掉。
45             for(int k=1; k<=j; k++ ) //给此孩子k元,得到的最大攻击力。
46                 dp[t][j]=max(dp[t][j], min(dp[t][j-k], dp[e.to][k]));
47         }
48         flag=1; //标记是否叶子。
49     }
50     if(flag==0)    memset(dp[t], 0, sizeof(dp[t]));     //叶子节点
51 
52     //本节点的决策是“仅有1组的分组背包”模型: 必须且最多买1个。(不买就只能靠孩子来防御)
53     for(int j=m; j>=0; j-- )  //容量
54     {
55         int k=1;
56         for( ; k<cnt[t] && !tow[t][k].pri; k++ )  //不用钱的,只留1个power最大的.
57             if(tow[t][k-1].pow > tow[t][k].pow)
58                 swap(tow[t][k],tow[t][k-1]);
59 
60         for( k-- ; k<cnt[t]; k++)    //物品
61         {
62             if(j-tow[t][k].pri>=0)
63                 dp[t][j]=max(dp[t][j], dp[t][j-tow[t][k].pri] + tow[t][k].pow );    //可以直接挡
64         }
65     }
66 }
67 
68 int main()
69 {
70     freopen("input.txt", "r", stdin);
71     int t, a, b; cin>>t;
72     while(t--)
73     {
74         edge_cnt=0;
75         memset(head, -1, sizeof(head));
76         memset(dp, 0x3f, sizeof(dp));   //注意初始化
77 
78         scanf("%d",&n);
79         for(int i=1; i<n; i++)  //无向图
80         {
81             scanf("%d%d",&a,&b);
82             add_node(a,b);
83             add_node(b,a);
84         }
85 
86         scanf("%d",&m);     //钱。
87         for(int i=1; i<=n; i++)
88         {
89             scanf("%d",&cnt[i]);
90             for(int j=0; j<cnt[i]; j++)
91                 scanf("%d%d", &tow[i][j].pri, &tow[i][j].pow);
92             sort(tow[i], tow[i]+cnt[i], cmp);   //免费的排前面
93         }
94         DFS(1, -1);
95         printf("%d
",dp[1][m]);
96     }
97     return 0;
98 }
AC代码
原文地址:https://www.cnblogs.com/xcw0754/p/4820463.html