洛谷 P1802. 5 倍经验日--01背包变形
思路对了 方法对了 状态方程写出来了 但就是着急 想直接写出一维形式(本质还是对二维形式不熟练) 但没有考虑到01背包j要逆序遍历 所以还是没做出来
5 倍经验日
题目背景
现在乐斗有活动了!每打一个人可以获得 5 倍经验!absi2011 却无奈的看着那一些比他等级高的好友,想着能否把他们干掉。干掉能拿不少经验的。
题目描述
现在 absi2011 拿出了 \(x\) 个迷你装药物(嗑药打人可耻…),准备开始与那些人打了。
由于迷你装药物每个只能用一次,所以 absi2011 要谨慎的使用这些药。悲剧的是,用药量没达到最少打败该人所需的属性药药量,则打这个人必输。例如他用 \(2\) 个药去打别人,别人却表明 \(3\) 个药才能打过,那么相当于你输了并且这两个属性药浪费了。
现在有 \(n\) 个好友,给定失败时可获得的经验、胜利时可获得的经验,打败他至少需要的药量。
要求求出最大经验 \(s\),输出 \(5s\)。
输入格式
第一行两个数,\(n\) 和 \(x\)。
后面 \(n\) 行每行三个数,分别表示失败时获得的经验 \(\mathit{lose}_i\),胜利时获得的经验 \(\mathit{win}_i\) 和打过要至少使用的药数量 \(\mathit{use}_i\)。
输出格式
一个整数,最多获得的经验的五倍。
【数据范围】
- 对于 \(10\%\) 的数据,保证 \(x=0\)。
- 对于 \(30\%\) 的数据,保证 \(0\le n\le 10\),\(0\le x\le 20\)。
- 对于 \(60\%\) 的数据,保证 \(0\le n,x\le 100\), \(10<lose_i,win_i\le 100\),\(0\le use_i\le 5\)。
- 对于 \(100\%\) 的数据,保证 \(0\le n,x\le 10^3\),\(0<lose_i\le win_i\le 10^6\),\(0\le use_i\le 10^3\)。
题解
这道题不过是01背包的变形
状态方程\(f[i][j]\)表示在药品数量为j时,与前i个人交手形成的最大经验值
\(f[i][j] = max(f[i - 1][j] + lose[i], f[i - 1][j - v[i]] + win[i])(当 j >= v[i])\)
\(f[i][j] = f[i - 1][j] + lose[i](当 j < v[i] )\)
一定不要那么着急直接想写出一维的代码 反而会弄巧成拙
要慢慢分析 一层一层分析
先写出二维 再优化到一维
朴素写法(也能AC)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
long long f[N][N]; //题目数据范围过大
int n, m;
struct CHR{
int l, w, v; //与每个选手交战的三个属性
}ch[N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d%d%d", &ch[i].l, &ch[i].w, &ch[i].v);
for (int i = 1; i <= n; i ++ )
{
for (int j = 0; j <= m; j ++ ) //药数量可以使用完 用<=
{
if ( j >= ch[i].v) //当药品数量够与该选手交战时
{
f[i][j] = max(f[i - 1][j] + ch[i].l, f[i - 1][j - ch[i].v] + ch[i].w); //取 不与该选手交战直接投降 或与该选手交战 的经验最大值
}
else f[i][j] = f[i - 1][j] + ch[i].l;
}
}
printf("%lld\n", f[n][m] * 5);
return 0;
}
注意 该题答案要开成 long long 我们观察题目$ n <= 10^3, win, lose <= 10^6$ \(答案最大是 n*lose 用int存可能会溢出\)
一维优化
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
long long f[N];
int n, m;
struct CHR{
int l, w, v;
}ch[N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d%d%d", &ch[i].l, &ch[i].w, &ch[i].v);
for (int i = 1; i <= n; i ++ )
{
for (int j = m; j >= ch[i].v; j -- ) //类似01背包 j要倒着遍历
{
{
f[j] = max(f[j] + ch[i].l, f[j - ch[i].v] + ch[i].w); //取 不与该选手交战直接投降 或与该选手交战 的经验最大值
}
}
for (int j = ch[i].v - 1; j >= 0; j -- ) //这是与01背包不同的地方! 我们要存一下在j < ch[i].v时,也就是当前对阵对手失败时,加上失败的经验值
{ //如果不加,等到下一次循环时,我们在f[j - ch[i].v]里利用的f[]就是没有加上上一次对阵对手失败的经验值
f[j] += ch[i].l; //说人话就是就算你背包里这次装不下东西,也要加上这次的失败经验值
}
}
printf("%lld\n", f[m] * 5);
return 0;
}