当前位置: 首页 > news >正文

【题解】洛谷 P4037 [JSOI2008] 魔兽地图 [树上背包]

题面:P4037 [JSOI2008] 魔兽地图 - 洛谷

乍一看挺唬人,但核心思想就是有两种不同的武器,

都可以用钱买,都有力量值,都有数量限制。

一种高端的能用低端的合成,低端的只能买。

问给你固定的金币数,最多能提升多少武力值?

解析:

题目都暗示你了,肯定是树上背包,但不好想。

一步步的思考,对于每一种武器:

如果是低端武器,那么分成两部分,一部分留给合并高端武器,一部分转化为武力值。

如果是高端武器,先递归所有合成需要的低端武器。

枚举对于每一个低端武器使用的金币数,再合并。

除了最高端的武器,其他高端武器也都要留一部分给更高端的武器合并。

那这样初步的状态构造就有了:

f[x][i][j]表示处理武器 x 时,预留 i 个 x 用于合成更高级装备,
最后花费 j 金币能获得的最大力量值
p[N]: 装备提供的力量值
a[N]: 基本装备的单价,高级装备是合成所需的总金币
n 是装备的种类数,m 是金币数

更细分的说一下,如果是最低端的武器 x:

数组 sum 存储这个武器最多可以买多少个,sum[x] = m / a[x]。

枚举 i 为合成武器 x 的数量 (i <= sum[x]),j 为转化为力量值的数量 (j <= i):

f[x][i-j][i*a[x]]=p[x]*j

如果是高级装备 x,枚举每一个要用到的低级装备 y递归去跑一遍。

sum[x] = min(sum[x], sum[y] /合成 x 所需 y 数量)

顺便处理下 a[x]sum[x] = m / a[x]

枚举总的合成的 x 数量 i (i <= sum[x]),再枚举每一个 y。

把金币分成两部分,一部分花在当前 y 节点,另一部分花在前面枚举的其它 y 节点

开个数组 g 记录当前 x 花 k 金币能得到的最多力量值

枚举 ga 为当前 x 武器总使用金币数gb 为分配给当前 y 的金币数

更新当前 g[ga] 时就用:

max(g[ga - gb] + f[y][i * 合成 x 所需 y 数量 ][gb])

g[ga  -gb]: 花费 ga - gb 金币在之前的 y 上能获得的最大力量值

那一坨 f 是因为我们要预留 i *合成 x 所需 y 数量个 y,因为你要 i 个 x,花费 gb 个金币。 

总合并时,枚举预留的 x 的数量 j (j <= i),再枚举花的金币 k,用 g[k] + p[x] * (i - j) 更新 f[x][j][k]。

最高级的武器还得单独处理,详见主函数。

有很多注释的代码

(可以跟着我上面的思路打代码)

#include<bits/stdc++.h>
using namespace std;const int N = 55, M = 2010;
//2 * N 多开点,之后会有莫名其妙的东西炸掉 
int f[N][2 * N][M];     //f[x][i][j]表示处理武器 x 时,预留 i 个 x 用于合成更高级装备,//最后花费 j 金币能获得的最大力量值
int p[N], a[N];      //p[N]: 装备提供的力量//a[N]: 基本装备的单价,高级装备是合成所需的总金币
int sum[N];          //sum[x]:武器 x最多能购买的数量 
int ans[M];          //最后答案存储 
int n, m;struct node {int y, num;
} ;
vector<node> G[N];   //我用 vector 存武器之间的依赖关系
bool v[N];   // v[x]:1 代表 x 不是最高端的武器,反之 0 代表是 
bool b[N];   // b[x]:1 代表 x 已经 dfs 过,反之 0 代表还没有
int g[M];    //临时最大值存储 void dfs(int x) {if (b[x]) {   //节省时间 return ;}b[x] = 1;if (G[x].size() == 0) {   //如果是最低端的武器 sum[x] = min(sum[x], m / a[x]);for (int i = 0; i <= sum[x]; i++) {   //合成武器 x 的数量for (int j = 0; j <= i; j++) {    //转化为力量值的数量f[x][i - j][i * a[x]] = p[x] * j;}}return ;    //处理完了就退出 }sum[x] = 1e9;    //初始化最大值 for (auto i: G[x]) {dfs(i.y);        //递归 y sum[x] = min(sum[x], sum[i.y] / i.num);a[x] += i.num * a[i.y];    //累计花费值 }sum[x] = min(sum[x], m / a[x]);for (int i = 0; i <= sum[x]; i++) {  // i: 总合成 x 数量int g[M];memset(g, -0x3f, sizeof(g)); g[0] = 0;//为什么负无穷?见主函数 f 初始化 for (auto j: G[x]) {for (int ga = m; ga >= 0; ga--) {   //当前 x 武器总使用金币数  int t = -1e9;   //最大值初始化//为什么负无穷?见主函数 f 初始化 for (int gb = 0; gb <= ga; gb ++) {   //分配给当前 y 的金币数 t = max(t, g[ga - gb] + f[j.y][i * j.num][gb]);//g[ga  -gb]: 花费 ga - gb 金币在之前的 y 上能获得的最大力量值//f[j.y][i * j.num][gb]:预留 i * j.num个 y,因为你要 i 个 x,花费 gb 个金币 //关于 ga 为什么要倒着遍历,因为我们不能取当前 y 武器更新过的 g[ga - gb]//只能使用上一个合成 x 需要的低端武器的 gg[ga] = t;    //更新当前 g }}}//全部 y 都递归完了,合并 for (int j = 0; j <= i; j++) {   //预留的 x 的数量for (int k = 0; k <= m; k++) {   //总共花的金币f[x][j][k] = max(f[x][j][k], g[k] + p[x] * (i - j));}} }//自己想想为什么按照这个顺序枚举更新,//for (int i = 0; i <= sum[x]; i++) 和 for (auto j: G[x])可以调换位置吗? 
} int main() {ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m;memset(v, 0, sizeof(v));for (int i = 1; i <= n; i++) {cin >> p[i];char s[5];  //得开多点,防止奇怪的空格 cin >> s;if (s[0] == 'A') {int C;cin >> C;for (int j = 1; j <= C; j++) {node mt;cin >> mt.y >> mt.num;v[mt.y] = 1;G[i].push_back(mt);} }else {cin >> a[i] >> sum[i];}}memset(f, -0x3f, sizeof(f));//为什么要负无穷?因为如果 f[i][j][k] 中间的 j 是你真的要预留 k 个才可以合法的//毕竟之后递归的时候要用到。 memset(ans, 0, sizeof(ans)); //为什么都是 0?你最少也就只能拥有 0 个力量for (int i = 1; i <= n; i++) if(!v[i]) {   //如果是最高端的武器 dfs(i);              //先递归处理 for (int j = m; j >= 0; j--) {   //总花费金币数量 for (int k = 0; k <= j; k++) {    //真正给 i 使用的金币数量 ans[j] = max(ans[j], ans[j - k] + f[i][0][k]);// f[i][0][k]表示不预留 i,花费 k 金币能获得的最大力量值//关于这里为什么 j 是倒着枚举的,因为我们不能取当前 i 武器更新过的 ans[j - k]//只能使用上一个最高端武器的 ans }}}cout << ans[m] << "\n";return 0;
}


文章转载自:

http://ZVZJfLT0.hhxwr.cn
http://V8l2pths.hhxwr.cn
http://sFfL1H4E.hhxwr.cn
http://uxNesw3q.hhxwr.cn
http://Bwd6NfaN.hhxwr.cn
http://BKxMpnM5.hhxwr.cn
http://GlOyEUxO.hhxwr.cn
http://i8My7njY.hhxwr.cn
http://bYlRBTOq.hhxwr.cn
http://C0m1fQ3d.hhxwr.cn
http://mEtIjffO.hhxwr.cn
http://IuWRmg1L.hhxwr.cn
http://brCIQQbg.hhxwr.cn
http://ooZzCzeG.hhxwr.cn
http://Zd7H2DS2.hhxwr.cn
http://dGhbSQ5m.hhxwr.cn
http://PFkUghPk.hhxwr.cn
http://Lm7KwqtI.hhxwr.cn
http://k39SQBd6.hhxwr.cn
http://MjgADFPi.hhxwr.cn
http://RPm9RaFK.hhxwr.cn
http://m8uiEPOf.hhxwr.cn
http://eUdeFtpw.hhxwr.cn
http://C5joi3rC.hhxwr.cn
http://quUQe4u3.hhxwr.cn
http://civUspAl.hhxwr.cn
http://bBWCGr2I.hhxwr.cn
http://XHX5ejHA.hhxwr.cn
http://p0PVzrHv.hhxwr.cn
http://wpkPaWt2.hhxwr.cn
http://www.dtcms.com/a/367443.html

相关文章:

  • 东土正创AI交通服务器再获北京市批量应用订单
  • Springboot集成Netty
  • 系统代理开启时,钉钉页面加载失败
  • 基于STM32的除臭杀菌等多功能智能健康鞋柜设计
  • 在 PyCharm 里怎么“点鼠标”完成指令同样的运行操作
  • 学习PaddlePaddle--环境配置-PyCharm + Conda​
  • 彻底搞懂面向对象分析(OOA)
  • 遇享会—金湾读书会—第四期—你好,陌生人——20250823
  • Drawdb与cpolar:数据库设计的远程协作解决方案
  • 【CS32L015C8T6】配置单片机时基TimeBase(内附完整代码及注释)
  • 深度剖析 DC - DC 转换器在新能源汽车中的关键应用
  • 【RNN-LSTM-GRU】第二篇 序列模型原理深度剖析:从RNN到LSTM与GRU
  • Scikit-learn Python机器学习 - 特征预处理 - 归一化 (Normalization):MinMaxScaler
  • [光学原理与应用-386]:ZEMAX -1064nm SESAM光纤种子源设计,需要学习哪些光学理论和工程知识?
  • @Autowired原理(四)
  • Mongo的增删改查
  • 裸签、Attach、Detach及其验签方式
  • 「数据获取」中国科技统计年鉴(1991-2024)Excel
  • 无人机防风技术难点解析
  • 【Unity知识分享】Unity接入dll调用Window系统接口
  • 异地多活架构:从“机房炸了”到“用户无感”的逆袭之路
  • 【系统架构设计(16)】软件架构设计二:软件架构风格:构建系统的设计模式与选择指南
  • 树形组件,支持搜索展示,自定义展示,支持vue2,vue3,小程序等等
  • 去中心化投票系统开发教程
  • Eclipse 常用搜索功能汇总
  • go面试题-什么是用户态和内核态
  • C++语言编程规范-常量
  • windows线程注入
  • LeetCode 48 - 旋转图像算法详解(全网最优雅的Java算法
  • ResNet(残差网络)-彻底改变深度神经网络的训练方式