题意:给你一棵n个结点的树,1号结点为敌人基地,叶子结点为我方基地。我们可以在任意一个结点至多修建一门大炮,每个结点有k种大炮,每种大炮有花费值和伤害值。问在给你m元的基础上,我们一定可以打败敌人的最大值。
题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4044
题解思路:首先这是很明显的树形dp,我们要求的是每个结点的最小攻击值中的最大值,才能确保敌人无论走哪条路我们都能将期击败。具体思路是两次dp,第一次dp找出在i结点还没选择大炮的情况下的最小防御值中的最大值(这里有点难理解,当初想了一个下午才勉强明白,自己太渣了),第二次dp在第一次dp基础上选择加入哪种大炮的防御值最大(明显的分组背包)。
附上自己写的ac代码:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <list>
#include <map>
#include <set>
#define For0(n) for(int i = 0; i < n; i++)
#define For1(n) for(int i = 1; i <= n; i++)
#define Forn_m(n,m) for(int i = n; i <= m; i++)
#define ForN_m(n,m) for(int i = n; i < m; i++)
#define MAX 1005
#define INF 0x3f
using namespace std;
int hp[MAX][205];
int T, n, m;
vector <int> vec[MAX];
bool vis[MAX];
int dp[MAX][205];
void dfs(int root)
{
bool flag = false;
int num = vec[root].size();
vis[root] = true;
For0(num)
if (!vis[vec[root][i]])
{
int a = vec[root][i];
dfs(a);
flag = true;
int t;
for (int k = m; k >= 0; k--)//第一次dp
{
t = 0;
for (int j = 0; j <= k; j++)
t = max(t, min(dp[root][k - j], dp[vec[root][i]][j]));
dp[root][k] = t;
}
}
if (!flag)//叶子结点
{
Forn_m(0, m)
dp[root][i] = hp[root][i];
return;
}
for (int k = m; k >= 0; k--)//第二次dp
for (int j = 0; j <= k; j++)
dp[root][k] = max(dp[root][k], dp[root][k - j] + hp[root][j]);
}
int main()
{
int u, v;
scanf("%d", &T);
while (T--)
{
For0(MAX)
vec[i].clear();
memset(vis, 0, sizeof(vis));
memset(dp, INF, sizeof(dp));
memset(hp, 0, sizeof(hp));
scanf("%d", &n);
Forn_m(2, n)
{
scanf("%d%d", &u, &v);
vec[u].push_back(v);
vec[v].push_back(u);
}
scanf("%d", &m);
int num, x, y;
For1(n)
{
scanf("%d", &num);
for (int j = 0; j < num; j++)
{
scanf("%d%d", &x, &y);
hp[i][x] = max(hp[i][x], y);
}
}
For1(n)
for (int j = 1; j <= m; j++)
hp[i][j] = max(hp[i][j], hp[i][j - 1]);
dfs(1);
printf("%d\n", dp[1][m]);
}
return 0;
}