P4037 [JSOI2008] 魔兽地图 题解
非常好的一道树上背包。
先分析题目。我们发现对于一种装备,它要么不升级,用自己来贡献答案;要么升级,获取高阶装备的贡献。因此普通树上背包的状态不够转移,考虑多添加一位。因此我们用
首先考虑叶子节点的初始化。对于每个叶子节点的装备,我们枚举其选了多少个(用
然后考虑转移。最外层枚举合成了多少个该物品(
需要注意,此题可能是个森林,因此在外面还要做一次背包。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 52;
const int M = 2005;
const int inf = 1e9;
struct edges{
int to, next, w;
};
edges edge[N << 1];
int n, m, cnt = 0;
int p[N], cost[N], lim[N], in[N], head[N], g[M], dp[N][2 * N][M], ans[M];
void add_edge(int u, int v, int w){
edge[++ cnt].to = v;
edge[cnt].next = head[u];
edge[cnt].w = w;
head[u] = cnt;
}
void dfs(int u){
if(! head[u]){
lim[u] = min(lim[u], m / cost[u]);
for(int i = 0; i <= lim[u]; i ++)
for(int j = 0; j <= i; j ++)
dp[u][j][i * cost[u]] = p[u] * (i - j);
return;
}
lim[u] = inf; cost[u] = 0;
for(int i = head[u]; i; i = edge[i].next){
int v = edge[i].to, w = edge[i].w;
dfs(v);
cost[u] += cost[v] * w;
lim[u] = min(lim[u], lim[v] / w);
}
lim[u] = min(lim[u], m / cost[u]);
for(int i = lim[u]; i >= 0; i --){
memset(g, -0x3f, sizeof g); g[0] = 0;
for(int ii = head[u]; ii; ii = edge[ii].next){
int v = edge[ii].to, w = edge[ii].w;
for(int j = m; j >= 0; j --){
int tmp = -inf;
for(int k = 0; k <= j; k ++)
tmp = max(tmp, g[j - k] + dp[v][i * w][k]);
g[j] = tmp;
}
}
for(int j = 0; j <= i; j ++)
for(int k = 0; k <= m; k ++)
dp[u][j][k] = max(dp[u][j][k], g[k] + (i - j) * p[u]);
}
}
int main(){
memset(dp, -0x3f, sizeof dp);
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i ++){
scanf("%d", &p[i]);
char c; cin >> c;
if(c == 'A'){
int cs; scanf("%d", &cs);
for(int j = 1; j <= cs; j ++){
int v, w; scanf("%d %d", &v, &w);
add_edge(i, v, w);
in[v] ++;
}
}else scanf("%d %d", &cost[i], &lim[i]);
}
for(int i = 1; i <= n; i ++){
if(in[i]) continue;
dfs(i);
for(int j = m; j >= 0; j --)
for(int k = 0; k <= j; k ++) ans[j] = max(ans[j], ans[j - k] + dp[i][0][k]);
}
printf("%d\n", ans[m]);
return 0;
}