DP模型——背包模型 01 (01背包类型)

大纲

在这里插入图片描述

这个关系不是特别正确,但不妨碍我们来讲

前置知识

01背包、完全背包、分组背包、多重背包①、多重背包②

直接放以前发的 博客

前言

对于背包问题,本质上就是有限制的选择问题

对于给定的物品选还是不选

01背包

纯板子

采药

没啥说的

// Problem: 采药
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/425/
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// Code by: ING__
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
#define re return

using namespace std;

int n, m;
int v[110];
int w[110];
int f[1010];

int main(){
	cin >> m >> n;
	for(int i = 1; i <= n; i++){
		cin >> v[i] >> w[i];
	}
	
	for(int i = 1; i <= n; i++){
		for(int j = m; j >= v[i]; j--){
			f[j] = max(f[j], f[j - v[i]] + w[i]);
		}
	}
	
	cout << f[m];
	
	re 0;
}

装箱问题

没啥说的,就是反过来问

// Problem: 装箱问题
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/1026/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Code by: ING__
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

int n, m;
int v[35];
int w[35];
int f[20010];

int main(){
	cin >> m >> n;
	for(int i = 1; i <= n; i++){
		cin >> v[i];
		w[i] = v[i];
	}
	
	for(int i = 1; i <= n; i++){
		for(int j = m; j >= v[i]; j--){
			f[j] = max(f[j], f[j - v[i]] + w[i]);
		}
	}
	
	cout << m - f[m];
	
	return 0;
}

阅读理解

宠物小精灵(也属于二维背包)

宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事。

一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物小精灵。

小智也想收服其中的一些小精灵。

然而,野生的小精灵并不那么容易被收服。

对于每一个野生小精灵而言,小智可能需要使用很多个精灵球才能收服它,而在收服过程中,野生小精灵也会对皮卡丘造成一定的伤害(从而减少皮卡丘的体力)。

当皮卡丘的体力小于等于0时,小智就必须结束狩猎(因为他需要给皮卡丘疗伤),而使得皮卡丘体力小于等于0的野生小精灵也不会被小智收服。

当小智的精灵球用完时,狩猎也宣告结束。

我们假设小智遇到野生小精灵时有两个选择:收服它,或者离开它。

如果小智选择了收服,那么一定会扔出能够收服该小精灵的精灵球,而皮卡丘也一定会受到相应的伤害;如果选择离开它,那么小智不会损失精灵球,皮卡丘也不会损失体力。

小智的目标有两个:主要目标是收服尽可能多的野生小精灵;如果可以收服的小精灵数量一样,小智希望皮卡丘受到的伤害越小(剩余体力越大),因为他们还要继续冒险。

现在已知小智的精灵球数量和皮卡丘的初始体力,已知每一个小精灵需要的用于收服的精灵球数目和它在被收服过程中会对皮卡丘造成的伤害数目。

请问,小智该如何选择收服哪些小精灵以达到他的目标呢?

输入格式

输入数据的第一行包含三个整数:N,M,K,分别代表小智的精灵球数量、皮卡丘初始的体力值、野生小精灵的数量。

之后的K行,每一行代表一个野生小精灵,包括两个整数:收服该小精灵需要的精灵球的数量,以及收服过程中对皮卡丘造成的伤害。

输出格式

输出为一行,包含两个整数:C,R,分别表示最多收服C个小精灵,以及收服C个小精灵时皮卡丘的剩余体力值最多为R。


花费1:精灵球数量

花费2:皮卡丘体力值

价值让我们求收服小精灵数量

状态表示:f[i, j, k] 表示所有从前 i 个物品中选,且花费1的花费不超过j,花费2的花费不超过 k 的选法的值

属性:最大值

状态计算:
f [ i ] [ j ] [ k ] = m a x ( f [ i − 1 ] [ j ] [ k ] , f [ i − 1 ] [ j − v 1 [ i ] ] [ k − v 2 [ i ] ] + 1 ) f[i][j][k]=max(f[i-1][j][k],f[i-1][j-v_1[i]][k-v2[i]] + 1) f[i][j][k]=max(f[i1][j][k],f[i1][jv1[i]][kv2[i]]+1)
那么最多首付的数量就是 f[K, N, M-1],因为皮卡丘的体力不能小于等于0,所以不能全消耗

最少消耗体力就看看枚举 m ,哪一个m使得 f[K, N, m] == f[K, N, M - 1]

背包问题求方案数

板子

题目要求的是在价值最大的时候的方案数是什么。

状态表示

f[i][j]表示从前 i 个物品中选,体积恰好为 j 的价值;g[i][j]表示从前 i 个物品中选,体积恰好为 j

// Problem: 背包问题求方案数
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/11/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Code by: ING__
// 
// Edited on 2021-07-27 12:34:36

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define ll long long
#define re return
#define Endl "\n"
#define endl "\n"

using namespace std;

const int N = 1010;
const int mod = 1e9 + 7;

int n, m;
int v[N];
int w[N];
int f[N]; // 不同体积对应的最大价值
int g[N]; // 不同体积对应的方案数

int main(){
	cin >> n >> m;
	for(int i = 1; i <= n; i++){
		cin >> v[i] >> w[i];
	}
	
	
	memset(f, -0x3f, sizeof(f));
	f[0] = 0;
	g[0] = 1;
	
	for(int i = 1; i <= n; i++){
		for(int j = m; j >= v[i]; j--){
			int maxx = max(f[j], f[j - v[i]] + w[i]);
			int s = 0;
			if(f[j] == maxx) s += g[j];
			if(f[j - v[i]] + w[i] == maxx) s = (g[j - v[i]] % mod + s % mod) % mod;
			f[j] = maxx;
			g[j] = s;
		}
	}
	
	int id = 0;
	
	for(int i = 0; i <= m; i++){
		if(f[i] > f[id]){
			id = i;
		}
	}
	
	int ans = 0;
	for(int i = 0; i <= m; i++){
		if(f[i] == f[id]){
			ans = (ans % mod + g[i] % mod) % mod;
		}
	}
	
	cout << ans << Endl;
	
	return 0;
}

数字组合

要及时转换

把和M看做容量

每个数看做体积为该数的物品,贡献也是该数

目标求出总体积恰好为 M 的方案数

状态表示

所有只从前 i 个物品中选,且总体积恰好是 j 的方案的集合

属性

数量

状态计算

所有不包括物品 i 的选法 和 所有包括物品 i 的选法

f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]的方案和 f [ i − 1 ] [ j − v i ] f[i-1][j-v_i] f[i1][jvi]的方案

f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − v i ] f[i][j] = f[i-1][j]+f[i-1][j-v_i] f[i][j]=f[i1][j]+f[i1][jvi]

Code
// Problem: 数字组合
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/280/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Code by: ING__
// 
// Edited on 2021-07-25 23:38:38

#include <iostream>

using namespace std;

int n, m;
int a[110];
int f[10010];

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }

    f[0] = 1; // f[0][0] 当一个都不选的时候,恰好选0个构成和为0的方案数为1

    for(int i = 1; i <= n; i++){
        for(int j = m; j >= a[i]; j--){
            f[j] += f[j - a[i]];
        }
    }

    cout << f[m];

    return 0;
}

具体方案

状态表示与01背包相同
集合分析与01背包完全相同

选择方案的判断,就是看每次方案的选择是哪一个
如果我们此时状态的计算f[i, j]的值是不选第i个物品的价值,即此时最大的价值就是f[i - 1, j],那么我们此时的方案就是不选第i个物品

同理,如果从右边取最大值,我们的答案就是选第i个

即我们判断这个值是等于哪一个来判断第i个物品选还是不选了

这样我们就可以从后往前推一遍来判断某个物品选还是不选了

为什么要从后往前推呢?

只有从后往前推才能逐一的判断从最优解的答案开始往前推

就是这个问题本质上是相当于最短路问题(这里叫最短路只是为了与前面那个最短路的类型类似)。我们知道了当前这个点f[i][j]的值,我们根据我们求最大值的定义,我们看看是长上一个的哪一个状态,即f[i - 1][j]和f[i - 1][j - v[i]] + w[i]这两个中哪一个转移过来的,就能倒退我们的路径了;如果正推的话,显然我们不能一次就判断出来哪一个到最后确定的方案中选还是不选

而答案还要保证字典序是最小的,那么我们在选取答案的时候要尽可能的拿到即选

而为了求到这个方案,那么我们上面推到那些就得全部颠倒,即从n到1开始转移,从1-n开始推出方案

#include <iostream>

using namespace std;

int n;
int m;
int f[1010][1010];
int w[1010];
int v[1010];

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        cin >> v[i] >> w[i];
    }
    
    for(int i = n; i; i --){ // 从后往前
        for(int j = 0; j <= m; j++){
            f[i][j] = f[i + 1][j]; // 与正着的意义一样,都是判断上一个选还是不选
            if(j >= v[i]){
                f[i][j] = max(f[i][j], f[i + 1][j - v[i]] + w[i]);
            }
        }
    }
    
    int i = 1, j = m;
    while(i <= n){
        if(j >= v[i] && f[i + 1][j - v[i]] + w[i] >= f[i + 1][j]){ // 说明右边可以取最大值,即当前这个物品选了
            cout << i << " ";
            j -= v[i];
            i++;
        }
        else{
            i++;
        }
    }
    return 0;
}

简单应用

开心的金明

这题就是金明的预算方案简化版,本质上去除了从属关系,就成了裸的01背包问题

// Problem: 开心的金明
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/428/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Code by: ING__
// 
// Edited on 2021-07-26 20:53:58

#include <iostream>

using namespace std;

int n, m;
int v[30];
int w[30];
int f[30010];

int main(){
	cin >> m >> n;
	for(int i = 1; i <= n; i++){
		int vv, p;
		cin >> vv >> p;
		v[i] = vv;
		w[i] = vv * p;
	}
	
	for(int i = 1; i <= n; i++){
		for(int j = m; j >= v[i]; j--){
			f[j] = max(f[j], f[j - v[i]] + w[i]);
		}
	}
	
	cout << f[m];
	
	return 0;
}

结合

能量石

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值