洛谷 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;
}
posted @ 2024-04-16 17:24  MsEEi  阅读(17)  评论(0)    收藏  举报