DP----背包问题Knapsack入门与实战(C++)


方便自己预习也帮大佬们复习

概述

过程:
以01背包为例,在DP的思想之下,我们可以划分的最后一步一般是选择拿还是不拿、拿几件、拿了会产生什么样的状态、什么才是我们想要的最优状态,子问题则一般是我们如何得到这个最优子结构。再从我们确定的状态得出我们想要满足状态的转移方程。在处理顺序时,通常是内层循环枚举容积,外层循环枚举选择前几个。

思想:
别的背包基本上都是01背包的变形,通常在做的时候可以将它们关系着01背包来处理


入门背包题(必须先掌握的背包)

01背包——Bone Collect

HDUOJ测试链接

题目描述:
有N件物品,背包最大容量为V,每件物品都有自己的体积v_i与价值w_i,每件物品最多只能拿一件且不可分割,背包最多能带多少价值的东西?

输入描述:
先输入cass代表样例个数
每个样例第一行输入N和V (N <= 1000 , V <= 1000 )
第二行输入N个正整数代表每件物品的价值w_i
第三行输入N个正整数代表每件物品的体积v_i

输出描述:
每个样例输出一行整数N代表最大价值

样例:
输入:
1
5 10
1 2 3 4 5
5 4 3 2 1
输出:
14
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.确定状态:
  最后一步:在当前容积下,面临这个物品拿或者不拿两种情况
  子问题:拿后的剩余容积可以最大承载价值是多少?
2.转移方程:dp[i][j]表示在j容积下前i件物品最多能装多少价值
  dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);
  //前者为不拿,后者为拿
3.计算顺序:i从1到n,对应每个i,求出j从V到0的最优解
//注:若从前往后可能会出现自己调用自己这一行的情况也就是说可能一个物品会多次选取
//所以我们在子循环中要从大到小
4.初始化:dp数组全部清0维护最大值

思路图示:

i\dp\j 0 1 2 3 4 5 6 7 8 9 10
0 0 0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 1 1 1 1 1 1
2 0 0 0 0 2 2 2 2 2 3 3
3 0 0 0 3 3 3 3 5 5 5 5
4 0 0 4 4 4 7 7 7 7 9 9
5 0 5 5 9 9 9 12 12 12 12 14
//二维dp
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
#include <unordered_map>
#define rep1(i, a, n) for (int i = a; i <= n; i++)
#define rep2(i, a, n) for (int i = a; i >= n; i--)
#define mm(a, b) memset(a, b, sizeof(a))
#define elif else if
typedef long long ll;
const int int_max = 1 << 31 - 1;
using namespace std;
int dp[1005][1005];
int main()
{
    
    
    int cass;
    for (scanf("%d", &cass); cass;cass--)
    {
    
    
        int N, V;
        cin >> N >> V;
        mm(dp, 0);
        int v[1005], w[1005];
        rep1(i, 1, N) cin >> w[i];
        rep1(i, 1, N) cin >> v[i];
        rep1(i, 1, N)
            rep2(j, 0, V) 
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);
        cout << dp[N][V] << endl;
    }
    return 0;
}
//一维dp
//此时就建立了个类滚动数组,体积从大往小算防止自己这一行重复利用自己这一行导致的多次拿取某物品
//dp[j]表示在j体积下能容纳的最大价值,外层i从大到小使dp[j]不断更新
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
#include <unordered_map>
#define rep1(i, a, n) for (int i = a; i <= n; i++)
#define rep2(i, a, n) for (int i = a; i >= n; i--)
#define mm(a, b) memset(a, b, sizeof(a))
#define elif else if
typedef long long ll;
const int int_max = 1 << 31 - 1;
using namespace std;
int dp[1005];
int main()
{
    
    
    int T;
    for (scanf("%d", &T); T;T--)
    {
    
    
        int N, V;
        cin >> N >> V;
        int v[N + 5];
        int w[N + 5];
        rep1(i, 1, N) cin >> w[i];
        rep1(i, 1, N) cin >> v[i];
        mm(dp, 0);
        rep1(i, 1, N)
            rep2(j, V, v[i])
                dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        cout << dp[V] << endl;
    }
}

另外七种背包

完全背包——寒冰王座

HDU测试链接

题目大意:
死亡骑士去一家不会找钱的黑心商店买战前物资,有药瓶1(150块)、药瓶2(200块)、药瓶3(350块),每种药瓶都可以无限买。他的资金有n块,请问最少亏进去多少钱。
(模板题目:你有一个V容积的背包,有三种物品每种有自己的体积,请问你怎么装才能充分利用你的背包容积)

输入描述:
输入数据的第一行是一个整数T(1<=T<=100),代表测试数据的数量.然后是T行测试数据,每个测试数据只包含一个正整数N(1<=N<=10000),N代表死亡骑士手中钞票的面值.

输出描述
对于每组测试数据,请你输出死亡骑士最少要浪费多少钱给地精商人作为小费.

样例:
输入:
2
900
250
输出:
0
50
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
原理是一维的01背包,这里不再细说
但注意:01背包在子循环中防止自己调用自己,
       但完全背包就是要这么做,
       所以只需要子循环正向即可
转移方程:dp[i]表示有i块钱(容积为i的背包)所能购买的最大金额(能装下的最大重量)
dp[i]=max(dp[i],dp[i-v[i]]+v[i])
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
#include <unordered_map>
#define rep1(i, a, n) for (int i = a; i <= n; i++)
#define rep2(i, a, n) for (int i = a; i >= n; i--)
#define mm(a, b) memset(a, b, sizeof(a))
#define elif else if
typedef long long ll;
const int int_max = 1 << 31 - 1;
using namespace std;
int v[3] = {
    
    150, 200, 350};//提前打好三种药瓶的表
int dp[10005];//在背包体积为i时最多可以装多少东西
int main()
{
    
    
    int cass;
    for (scanf("%d", &cass); cass;cass--)
    {
    
    
        int V;
        cin >> V;
        mm(dp, 0);
        
        rep1(i, 0, 2)
            rep1(j, v[i], V)//01背包要担心是否会同行调用同行导致物品多拿,此时我们不需要担心
                dp[j] = max(dp[j], dp[j - v[i]] + v[i]);
        
        cout << V-dp[V] << endl;
    }
}

多重背包——抗震救灾!购买大米!

HDU测试链接

题目描述:
你有n的经费和m种袋大米,每种大米都有每袋的标价、每袋的重量、该种大米的袋数,请问利用你的经费购买的最重的大米重量
(模板题目:你有n容积背包,有m种物品,每种物品都有自己的体积、自己的价值、和这种物品的个数,请问装的最大价值)

输入描述:
输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和m(1<=n<=100, 1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。

输出描述:
对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。

样例:
输入:
8 2
2 100 4
4 100 2
输出:
400
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
提示:

二进制转换
二进制转换是一种合理分配物品的手段,有效避免了一个一个分配的高复杂度
通过这种手段我们可以将多重背包转化为01背包
合并的块数从1开始,后面逐层*2,在最后不足以分配更大的2的n次幂时剩余的再合并一次
这种方法可以使总物品分块组合成一个个独立的小物品
例如:20–>1、2、4、8、5

下面是代码实现

int V[1000], W[1000];//重组的物品体积与价值
void manage(int x,int v,int w)//x是这种物品的个数,v是每个物品的体积,w是每个物品的价值
{
    
    
    int cnt = 1, t = 1;
    while(x>=t)
    {
    
    
        V[cnt] = v * t;
        W[cnt++] = w * t;//这t件物品融合一下
        x -= t;
        t <<= 1;//下一次应是这次的两倍
    }
    if(x)//剩余的再合并
        V[cnt] = v * x, W[cnt++] = w * x;
}

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:多重背包的本质也是01背包,将其转化为你最会的就不愁了

转移方程:dp[i]表示手里有i块钱最多能买多重的大米

竞赛思维:将不会的转化为自己拿手的
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
#include <unordered_map>
#define rep1(i, a, n) for (int i = a; i <= n; i++)
#define rep2(i, a, n) for (int i = a; i >= n; i--)
#define mm(a, b) memset(a, b, sizeof(a))
#define elif else if
typedef long long ll;
const int int_max = 1 << 31 - 1;
using namespace std;
int v[1000], w[1000];
int dp[1005];
int n, m;
int cnt;
struct rice//每种大米
{
    
    
    int rv, rw, rnum;//每种物品的体积,价值,个数
} r[1000];

void riceCont()//二进制优化为01背包,这里我做了全转化
{
    
    
    cnt = 1;
    rep1(i,1,m)
    {
    
    
        int t = 1;
        while(r[i].rnum>=t)
        {
    
    
            v[cnt] = r[i].rv * t;
            w[cnt++] = r[i].rw * t;
            r[i].rnum -= t;
            t <<= 1;
        }
        if(r[i].rnum)
            w[cnt] = r[i].rw * r[i].rnum, v[cnt++] = r[i].rv * r[i].rnum;
    }
}

int main()
{
    
    
    int T;
    for (scanf("%d", &T); T;T--)
    {
    
    
        cin >> n >> m;
        mm(v, 0);
        mm(w, 0);
        mm(dp, 0);
        rep1(i, 1, m) cin >> r[i].rv >> r[i].rw >> r[i].rnum;
        riceCont();
        rep1(i, 1, cnt-1) rep2(j, n, v[i]) dp[j] = max(dp[j], dp[j - v[i]] + w[i]);//max(不拿,拿)
        cout << dp[n] << endl;
    }
}

精准装满的多重背包——Coin Change

LintCode测试链接

题目描述:
给出不同面额的硬币以及一个总金额. 写一个方法来计算给出的总金额可以换取的最少的硬币数量. 如果已有硬币的任意组合均无法与总金额面额相等, 那么返回 -1.

输入描述:
不同硬币的面值coins
要组成的钱数amount

样例
输入:
[1, 2, 5]
11
输出:
3
解释: 11 = 5 + 5 + 1

输入:
[2]
3
输出:
-1

注意事项:
你可以假设每种硬币均有无数个
总金额不会超过10000
硬币的种类数不会超过500, 每种硬币的面额不会超过100
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
//可视作完全背包问题
1.确定状态(dp[i]代表什么)
  *最后一步(最优化策略中使用的最后一枚硬币是哪个coin)
  *化成子问题(最少的硬币拼出更小的钱数amount-coin[i]2.转移方程--想法化为式子
  dp[amount]=min(dp[amount-coin[0]]+1,dp[amount-coin[1]]+1,dp[amount-coin[2]]+1,......,dp[amount-coin[len]]+1)
3.思考初始化条件与边界情况(为第二点完善细节)
  dp[0]=0
  若不能拼出x,dp[x]正无穷
4.计算顺序 
  dp[0],dp[1],dp[2].............
5.消除冗余,加速计算(记录、记忆化原理)
class Solution {
    
    
public:
    int coinChange(vector<int> &coins, int amount) {
    
    
        int len=coins.size();
        int dp[amount+1];//从0-amount有(amount+1)种选择,数组要开得比它大
        //dp[n]表示在剩n块钱时最少的组合个数
        
        dp[0]=0;//初始化
        int int_max=(1<<31)-1;//int的最大值
        
        for(int i=1;i<=amount;i++)
        {
    
    
            dp[i]=int_max;//初始化
            for(int j=0;j<len;j++)
            {
    
    
                if(i>=coins[j]&&dp[i-coins[j]]!=int_max)//限制:剩的i块钱必须被成功分割
                dp[i]=min(dp[i-coins[j]]+1,dp[i]);//最优化选择且记录(与记忆化搜索一个原理)
            }
        }
        
        if(dp[amount]==int_max) return -1;//找不到
        return dp[amount];
    }
};

分组背包——通天

洛谷测试链接

题目描述
自01背包问世之后,小 A 对此深感兴趣。一天,小 A 去远游,却发现他的背包不同于01背包,他的物品大致可分为k组,每组中的物品相互冲突,现在,他想知道最大的利用价值是多少。

输入格式
两个数m、n,表示一共有 n 件物品,容积为 m。
接下来 n 行,每行 3 个数v_i,w_i,s_i,表示物品的体积,价值,所属组数。

输出格式
一个数,最大的利用价值。

样例
输入
45 3
10 10 1
10 5 1
50 400 2
输出
10

说明/提示
1≤m,n≤1000。
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

//什么背包都摆脱不了01背包,其实这类可以分为多维背包(每加一维,多一个循环)
思路:
1.将组数 0——MAX 枚举出来,保证每组选一个或者不选就行
2.注意内层两个循环,要保证不会多选就让枚举体积循环放外面,枚举物品放里面
  与体积从大到小一个道理,若循环顺序颠倒可能导致同一组多次利用
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
typedef long long ll;
const int INF = 0x7FFFFFFF;
using namespace std;
typedef long long ll;

int v[1005], w[1005],s[1005];//物品占体积,价值,所占组数

int nSet = 0;//最多的组数
int dp[1005] = {
    
    0};

int main()
{
    
    
    int m, n;
    cin >> m >> n;
    for (int i = 1; i <= n;i++)
        cin >> v[i] >> w[i] >> s[i], nSet = max(nSet, s[i]);//找出枚举组数的范围

    for (int set = 1; set <= nSet;set++) //枚举组
        for (int j = m; j >= 0;j--) //枚举体积
            for (int i = 1; i <= n;i++) //枚举物品(要放在最里面,保证每一组内的物品最多有一个被加入背包)
                if(s[i]==set&&j>=v[i])
                    dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
    
    cout << dp[m] << endl;
    return 0;
}

二维费用背包——装载食物

洛谷测试链接

题目描述:
(引申为模板) 有一个最大容积和最大承载质量分别为V和M的背包,有N个物品且最多只能用一次,每个物品都有自己的体积v_i,质量m_i,价值w_i,问这个背包最多能装多大价值的东西

输入格式
第一行 两个数 最大容积V(<400)和最大承载质量M(<400)
第二行 一个数 食品总数N(<50).
第三行-第3+N行
每行三个数 体积v_i(<400) 质量m_i(<400) 价值w_i(<500)

输出格式
一个数 所能装的最大价值(int范围内)

样例
输入
320 350
4
160 40 120
80 110 240
220 70 310
40 400 220
输出
550
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
//这和01背包基本上没区别01背包的基础上加一维并加一重循环
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
typedef long long ll;
const int INF = 0x7FFFFFFF;
using namespace std;
typedef long long ll;
int main()
{
    
    
    int V, M;
    cin >> V >> M;//容积,最大承载重量
    int N;
    cin >> N;
    int v[N + 10], m[N + 10], w[N + 10];//体积,重量,价值

    for (int i = 1; i <= N; i++)
        cin >> v[i] >> m[i] >> w[i];

    int dp[410][410] = {
    
    0};//dp[i][j]表示最大承载质量为i与容积为j的背包所带的最大价值

    for (int i = 1; i <= N;i++)
        for (int j = M; j >= m[i];j--)//俩01背包的循环。。。。。
            for (int k = V; k >= v[i]; k--)
                dp[j][k] = max(dp[j][k], dp[j - m[i]][k - v[i]] + w[i]);
    
    cout << dp[M][V];
    return 0;
}

混合背包——观赏樱花

洛谷测试链接

题目描述:
生物学家大佬每天上学前都喜欢在路上看樱花,而且大佬知道如何看樱花(有种樱花一遍过,有种樱花最多看flag_i遍,有种樱花可以看无数遍),每天他都有一个固定的时间段可以用来赏花,有N种樱花,每种樱花都有自己的需要观赏的时间t_i、观赏一遍后能获得的满足感w_i以及每种樱花的flag_i(0代表可观赏无数遍,1代表最多观赏一遍,其余代表最多观赏的次数),问这段时间内最多能收获多少满足感。

输入描述:
第一行输入时间段h1:m1 h2:m2(0<=h1,h2<=23,0<=m1,m2<=59)然后输入一个N代表花的种数(开始到结束时间不超过1000)
想吐槽:什么闲情雅致能让他半夜去赏花…
下面N行每一行输入三个整数t_i w_i flag_i分别代表观看第i种樱花要的时间、获取的满足感、每种花的flag(详见题目)

输出描述:
一个整数代表这段时间内大佬获得的最大的满足感

样例:
输入
6:50 7:00 3
2 1 0
3 3 1
4 5 4
输出
11
样例解释:
赏第一棵樱花树1次,赏第三棵樱花树 2 次。

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.01物品与多重物品跟多重背包一样用二进制优化物品
2.完全物品独立设出来一个数组标记一下

在循环的时候扫到01与多重物品时按01的来写,扫到完全物品用完全写
//我写完看别人的题解时,普遍有一缺点:完全背包的个数先设很大再一起二进制优化
//危险:数组容易开小,容易MLE
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cstdio>
#include <cmath>
typedef long long ll;
const int INF = 0x7FFFFFFF;
using namespace std;
typedef long long ll;
int T;
int N;
int t[100005], w[100005], flag[100005];//敲进去的每种花的用时、满足感、flag
int manaT[100005], manaW[100005], manaFlag[100005];//组合后的
int cnt = 1;//组合后的组数
void manage()
{
    
    
    for (int i = 1; i <= N; i++)
    {
    
    
        if (flag[i])//若01或多重就二进制一下
        {
    
    
            int x = 1;
            while (flag[i] >= x)
            {
    
    
                manaT[cnt] = t[i] * x;
                manaW[cnt] = w[i] * x;
                manaFlag[cnt++] = 1;//01与多重物品标记为1
                flag[i] -= x;
                x <<= 1;
            }
            if (flag[i])
                manaT[cnt] = t[i] * flag[i], manaW[cnt] = w[i] * flag[i], manaFlag[cnt++] = 1;
        }

        else//若完全就自己开一个
        {
    
    
            manaT[cnt] = t[i];
            manaW[cnt] = w[i];
            manaFlag[cnt++] = 0;//完全物品标记为0
        }
    }
}
int main()
{
    
    
    int h1, m1, h2, m2;
    scanf("%d:%d%d:%d", &h1, &m1, &h2, &m2);
    T = h2 * 60 + m2 - h1 * 60 - m1;//计算总时间(背包总容量)

    cin >> N;
    for (int i = 1; i <= N; i++)
        cin >> t[i] >> w[i] >> flag[i];
    manage();

    int dp[T + 10] = {
    
    0};//dp[i]表示在前i时间获取最大满足感
    for (int i = 1; i < cnt; i++)
    {
    
    
        //下面是三种背包物品的不同递推操作
        if (manaFlag[i])//01与多重
        {
    
    
            for (int j = T; j >= manaT[i]; j--)
                dp[j] = max(dp[j], dp[j - manaT[i]] + manaW[i]);
        }
        else//完全
        {
    
    
            for (int j = manaT[i]; j <= T; j++)
                dp[j] = max(dp[j], dp[j - manaT[i]] + manaW[i]);
        }
    }
    
    cout << dp[T] << endl;
    return 0;
}

有依赖性物品的背包——金明的预算方案

洛谷测试链接

题目描述
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过n元钱就行”。今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:

主件 附件
电脑 打印机,扫描仪
书柜 图书
书桌 台灯,文具
工作椅

如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有不超过两个的附件。每个附件对应一个主件,附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的n元。于是,他把每件物品规定了一个重要度,分为5等:用整数1∼5表示,第5等最重要。他还从因特网上查到了每件物品的价格(都是10元的整数倍)。他希望在不超过n元的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第j件物品的价格为v_j,重要度为w_j,共选中了k件物品,编号依次为j_1,j_2,…,j_kj ,则所求的总和为:v_1 * w_1 + … + v_kj * w_kj。
请你帮助金明设计一个满足要求的购物单。

输入格式
第一行有两个整数,分别表示总钱数n和希望购买的物品个数m。
第二到第(m+1)行,每行三个整数,第(i+1)行的整数v_i,w_i,flag_i分别表示第i件物品的价格、重要度以及它对应的的主件。如果flag_i=0则说明这个物品自己就是主件

输出格式
输出一行一个整数表示答案。

样例
输入
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出
2200

数据规模
对于全部的测试点,保证1<=n<=3.2*104,1<=m<=60,v_i<=104,1<=w_i<=5,0<=q_i<=m,答案不超过 2 * 105
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:

这题与分组背包有点像
只需要将附件跟主件合成一组,用二维数组,前面代表第几组,后面代表第几组的第几个

保证主件必拿,附件选择不拿、拿一个(拿第几个)、拿两个四种情况比大小
但注意:保证范围才能递推
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
typedef long long ll;
const int INF = 0x7FFFFFFF;
using namespace std;
typedef long long ll;

int v[100][3], w[100][3]; //v[i][j]表示第i套物品的前j件的体积,w[i][j]表示第i套物品的前j件价值
int V;//背包容量
int n;//物品个数

int main()
{
    
    
    cin >> V >> n;
    for (int i = 1; i <= n;i++)//优化:主附件并为一个组合,每次遇到附件就将它挪到主件那一组
    {
    
    
        int a, b, c;
        cin >> a >> b >> c;//a表示这件物品的体积,a*b表示这件物品的价值,c表这件物品的主件情况
        if(!c)//若为主件
            v[i][0] = a, w[i][0] = a * b;
        else//若为附件
        {
    
    
            if(!w[c][1])//主件后面第一个没有被占,放在第一个
                v[c][1] = a, w[c][1] = a * b;
            else//被占了,放在第二个
                v[c][2] = a, w[c][2] = a * b;
        }
    }

    int dp[32010];
    for (int i = 1; i <= n;i++)
    {
    
    
        for (int j = V; j >= v[i][0] && v[i][0]; j--)//稍微优化一下时间,记住:附件是没有自己的i的(地位好低)
        {
    
    
            dp[j] = max(dp[j], dp[j - v[i][0]] + w[i][0]);//只选主件
            v[i][0] + v[i][1] > j ?: dp[j] = max(dp[j], dp[j - v[i][0] - v[i][1]] + w[i][0] + w[i][1]);//买主件与第一个附件
            v[i][0] + v[i][2] > j ?: dp[j] = max(dp[j], dp[j - v[i][0] - v[i][2]] + w[i][0] + w[i][2]);//买主件与第二个附件
            v[i][0] + v[i][1] + v[i][2] > j ?: dp[j] = max(dp[j], dp[j - v[i][0] - v[i][1] - v[i][2]] + w[i][0] + w[i][1] + w[i][2]);//买主件与两个附件
        }
    }
    cout << dp[V] << endl;
    return 0;
}

泛化物品背包——烹调方案

洛谷测试链接

题目描述
米其林餐厅快出餐了,有个人有T时间,还有一份食谱有n种菜,每一种菜都有两个参数a.b和做出来需要的时间。
每一种菜的美味度w随时间改变,如果在T时间内的t时刻完成第i样菜则得到a[i]-t*b[i]的美味度,并且为了上桌好看,每样菜最多只能做一遍。
请问在这T时间内他做出来的菜的美味度最大是多少。

输入描述
第一行是T和n,表达他有的时间和菜的样数
下面一行n个整数a[i]
下面一行n个整数b[i]
下面一行n个整数t[i]

输出描述
输出一个整数表示最大美味值

样例
输入:
74 1
502
2
47
输出:
408

数据范围
1<=n<=50
所有数组均小于100000
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:

每种菜在不同时刻都有自己的美味度,所以顺序不能乱放
要想递推最优解成立,就必须满足最优顺序
思考出来如何排序才最优

其余做法就是正常的01背包
需要考虑清楚到底哪个值才是正确的t时刻
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <cstring>
#include <cmath>
typedef long long ll;
const int INF = 0x7FFFFFFF;
using namespace std;
typedef long long ll;

struct dish//菜的a,b和用时t
{
    
    
    ll a, b, t;
} d[100];
bool cmp(dish a,dish b)//需要排序,因为物品因时间影响导致价值不确定,要保证合理安排(贪心安排)
{
    
    
    return a.b * b.t > b.b * a.t;//如果都从0时刻开始,安排美味度最大的排前面
}

ll T, n;
int main()
{
    
    
    cin >> T >> n;
    for (int i = 1; i <= n; i++)
        cin >> d[i].a;
    for (int i = 1; i <= n; i++)
        cin >> d[i].b;
    for (int i = 1; i <= n; i++)
        cin >> d[i].t;
    sort(d + 1, d + 1 + n, cmp);

    ll dp[100005] = {
    
    0};
    ll max1 = 0;
    for (int i = 1; i <= n; i++)
    {
    
    
        for (int j = T; j >= d[i].t; j--)
        {
    
    
            dp[j] = max(dp[j], dp[j - d[i].t] + d[i].a - j * d[i].b);//正常01背包价值模拟,要注意时间是j
            max1 = max(max1, dp[j]);//用的是该方案中最后的时间,不一定时间越靠后越好
        }
    }
    cout << max1 << endl;
    return 0;
}

背包九讲内最后一讲是隐晦繁多的背包问法,就不再细细说明
但只要掌握了背包原理,做别的背包都不会太卡

猜你喜欢

转载自blog.csdn.net/SnopzYz/article/details/113619902