从0-1背包到完全背包

首先讲一讲初始化小tip
在这里插入图片描述

01背包

不进行空间优化

根据状态转移方程
在这里插入图片描述

我们很容易得出

#include<bits/stdc++.h>

using namespace std;

const int MAXN = 1005;
int w[MAXN];    // 重量 
int v[MAXN];    // 价值 
int f[MAXN][MAXN];  // f[i][j], j重量下前i个物品的最大价值 

int main() 
{
    int n, m;   
    cin >> n >> m;
    for(int i = 1; i <= n; ++i) 
        cin >> w[i] >> v[i];

    for(int i = 1; i <= n; ++i) 
        for(int j = 1; j <= m; ++j)
        {
            //  当前重量装不进,价值等于前i-1个物品
            if(j < w[i]) 
                f[i][j] = f[i-1][j];
            // 能装,需判断 
            else    
                f[i][j] = max(f[i-1][j], f[i-1][j-w[i]] + v[i]);
        }           

    cout << f[n][m];
    return 0;
}
进行空间优化
for(int i = 1; i <= n; ++i)
{
    for(int j = m; j >= 0; --j) 
        if(j >= w[i])
            f[j] = max(f[j], f[j-w[i]] + v[i]);
} 

问题就在于——为什么要逆序?

因为,如果正序的话,由于是一维数组,所以我们每次都会更新值,然后之前的取值就会丢失,那么如何才能get到之前的值呢?答案就是逆序
在这里插入图片描述
我们从一个具体的栗子来看是怎么get到之前的值的。
在这里插入图片描述所谓鱼和熊掌不可兼得,这个方法空间确实是优化了,但是我们只能知道最终的结果,但无法再回溯中间的选择,也就是无法根据最终结果来找到我们要选的物品组合。

模板题

传送门
这题的物品的价值和体积相同,我们运用崔带佬的常数优化,并且装满了就要及时退出,否则就会和我一样——
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

const int N = 5e4 + 10;
int sum[N], f[N];
int V, n; //最大容积和物品数量
int v[N]; //物品的体积

void solve(){
	scanf("%d%d", &V, &n);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d", &v[i]);
		sum[i] = sum[i - 1] + v[i]; //前缀和
	}
	for (int i = 1; i <= n ; ++i)
	{
		int bound = max(v[i], V - (sum[n] - sum[i])); //背包中空间留给选择的余地【常数优化】
		for (int j = V; j >= bound; j--)
		{
			f[j] = max(f[j], f[j - v[i]] + v[i]);
			if(f[V] == V) {printf("%d\n", V); return;} //如果已经装满咱就不装了【优化】
		}
	}
	printf("%d\n", f[V]);
}

int main(int argc, char const *argv[])
{
	solve();
	return 0;
}

②进阶题
传送门

题目大意:n种商品,m元钱,每种商品都有p,q,v属性,p表示价格,q表示买这种商品你需要带q元老板才愿意和你交易,v表示这种商品的实际价值。求问最多可以获得多少价值?

这题如果无脑背包你会惊讶 理所应当地发现第二个样例都不对,你会得到9,买了B和C。为了方便比对,我把样例贴出来——

2 10
10 15 10
5 10 5
3 10
5 10 5
3 5 6
2 7 3

为什么第二个样例都错了呢?这是因为我们再考虑第一个物品时只更新了f[10],然后轮到第二个物品时,需要借助上一层的f[5],但此时f[5]还没更新,所以第一个物品就相当于根本没有看到,所以看到第二个物品之后,最大价值就是第二个物品自身的价值,不存在和第一个物品的比较,然后再看到了第三个物品,这时我们需要的f[5]已经有了,所以第三个物品也装进了背包。我们没有看到第一个物品导致错误。怎么避免呢?我们来举个栗子——
比如A:p1 q1, B:p2 q2,然后,假设单独买A或者B的话,都是可以买到的。这时,若先买A,则你至少需要p1(A的实际价格)+q2(B的门槛钱)的钱;若先买B,则至少需要p2(B的实际价格)+q1(A的门槛钱)的钱。那肯定是花最少的钱咯,所以如果先买A再买B,那么p1+q2<p2+q1,转换一下,就是q1-p1>q2-p2,也就是说qi-pi大的先买。这里还得注意一点就是,排序的时候,得按照qi-pi从小到大排序,因为你买第n件商品的时候,是在比较你是否要先买第n件商品。
打个比方让大家更好地理解,比如说f(3, 10),是不是max(f(2, 10-p3)+v3, f(2, 10)),你会发现这个第一种情况f(2,10-p3)+v3中,是先买了第三件商品,也就是说排在后面的商品会先买。好的,排好序之后,就把问题就转换为不需要考虑顺序的问题了,那就是上面我们已经解决0/1背包问题了。这样,问题圆满解决了。

#include<bits/stdc++.h>
using namespace std;

const int M = 5e3 + 10;
const int N = 5e2 + 10;
int f[M];
struct Item
{
	int p, q, v;//p是实际价格,q是门槛钱,v是实际价值
}items[N];
bool cmp(Item a, Item b){
	return a.q - a.p <= b.q - b.p;
}

void solve(){
	int n, m; //n种商品,有m元钱
	while(scanf("%d%d", &n, &m) != EOF){
		memset(f, 0, sizeof f);
		for (int i = 1; i <= n; ++i)
		{
			scanf("%d%d%d", &items[i].p, &items[i].q, &items[i].v);
		}
		sort(items + 1, items + n + 1, cmp); //你是从1开始的,就要从1开始排序啊
		for (int i = 1; i <= n; ++i)
		{
			for (int j = m; j >= items[i].q; --j)
			{
				f[j] = max(f[j], f[j - items[i].p] + items[i].v);
			}
		}
		printf("%d\n", f[m]);
	}
}

int main(int argc, char const *argv[])
{
	solve();
	return 0;
}

完全背包

完全背包和01背包唯一的区别在于——每种物品的个数是无限的。

不进行空间优化

比01背包多套一层循环,因为第i种物品可以选0个、1个…V/v[i]向下取整个。比较笨拙的一种方法,完全套用01背包。
不仅空间消耗多,时间也TLE了QWQ。
所以,我们继续优化。

进行空间优化
#include<bits/stdc++.h>
using namespace std;
const int MAX = 1e3 + 10;
int v[MAX], w[MAX];//每个物品的体积和价值
int f[MAX];
int N, V;//物品种数和背包容积
void solve(){
    scanf("%d%d", &N, &V);
    for(int i = 1; i <= N; i++) scanf("%d%d", &v[i], &w[i]);
    for(int i = 1; i <= N; i++){
        for(int j = v[i]; j <= V; j++) f[j] = max(f[j], f[j - v[i]] + w[i]);
    }
    printf("%d\n", f[V]);
}
int main(){
    solve();
    return 0;
}

可能聪明的你已经看出来了,这个地方的优化和上面01背包的优化唯一的区别在于j是正序遍历的,为什么这里又可以正序了呢?
在这里插入图片描述

模板题

传送门
没啥好说的,直接套模板,记得从前往后是从v[i]到V

#include<bits/stdc++.h>
using namespace std;

const int N = 1e7 + 10;
int f[N];
int T, n; //规定时间和草药数量
int t[N], v[N]; //每种草药所需时间和价值

void solve(){
	scanf("%d%d", &T, &n);
	for (int i = 1; i <= n; ++i)
	{
		scanf("%d%d", &t[i], &v[i]);
	}
	for (int i = 1; i <= n ; ++i)
	{
		for (int j = t[i]; j <= T; j++)
		{
			f[j] = max(f[j], f[j - t[i]] + v[i]);
		}
	}
	printf("%d\n", f[T]);
}

int main(int argc, char const *argv[])
{
	solve();
	return 0;
}

传送门
英文题有点不打友好,我也不想看,于是贴心的我把中文给同志们po出来——

在 ACM 能够开展之前,必须准备预算,并获得必要的财力支持。该活动的主要收入来自于 Irreversibly Bound Money (IBM)。思路很简单。任何时候,某位 ACM 会员有少量的钱时,他将所有的硬币投入到小猪储钱罐中。这个过程不可逆,因为只有把小猪储钱罐打碎才能取出硬币。在足够长的时间之后,小猪储钱罐中有了足够的现金,用于支付 ACM 活动所需的花费。
但是,小猪储钱罐存在一个大的问题,即无法确定其中有多少钱。因此,我们可能在打碎小猪储钱罐之后,发现里面的钱不够。显然,我们希望避免这种不愉快的情况。唯一的可能是,称一下小猪储钱罐的重量,并尝试猜测里面的有多少硬币。假定我们能够精确判断小猪储钱罐的重量,并且我们也知道给定币种的所有硬币的重量。那么,我们可以保证小猪储钱罐中最少有多少钱。
你的任务是找出最差的情形,即判断小猪储钱罐中的硬币最少有多少钱。我们需要你的帮助。不能再贸然打碎小猪储钱罐了!
输入
输入包含 T 组测试数据。输入文件的第一行,给出了 T 的值。
对于每组测试数据,第一行包含 E 和 F 两个整数,它们表示空的小猪储钱罐的重量,以及装有硬币的小猪储钱罐的重量。两个重量的计量单位都是 g (克)。小猪储钱罐的重量不会超过 10 kg (千克),即 1 <= E <= F <= 10000 。每组测试数据的第二行,有一个整数 N (1 <= N <= 500),提供了给定币种的不同硬币有多少种。接下来的 N 行,每行指定一种硬币类型,每行包含两个整数 P 和 W (1 <= P <= 50000,1 <= W <=10000)。P 是硬币的金额 (货币计量单位);W 是它的重量,以 g (克) 为计量单位。
输出
对于每组测试数据,打印一行输出。每行必须包含句子 “The minimum amount of money in the piggy-bank is X.” 其中,X 表示对于给定总重量的硬币,所能得到的最少金额。如果无法恰好得到给定的重量,则打印一行 “This is impossible.” 。

纵使它变成了求最小价值,还是逃不脱完全背包的宿命,因为每种硬币都是无限支取的啊,还记得吗?要完全装满,我们初始化的小技巧。所以代码就很简单了,不要被杭电的嘤语题吓到嘛!害~

#include<bits/stdc++.h>
using namespace std;

const int N = 505;
const int M = 1e4 + 5;
const int INF = 0x3f3f3f3f;

int v[N], w[N];//金额和重量
int t, n;
int W, W1, W2;//W是真正能装的重量
int f[M];

void solve(){
    scanf("%d", &t);
    while(t--){
        scanf("%d%d", &W1, &W2);
        W = W2 - W1;
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) scanf("%d%d", &v[i], &w[i]);
        memset(f, INF, sizeof f);//要全部都是无限大
        f[0] = 0;//除了0号元素
        for (int i = 1; i <=n; ++i)
        {
            for (int j = w[i]; j <= W; ++j)
            {
                f[j] = min(f[j], f[j - w[i]] + v[i]);
            }
        }
        if(f[W] == INF) printf("This is impossible.\n");
        else printf("The minimum amount of money in the piggy-bank is %d.\n", f[W]);
    }
}

int main(int argc, char const *argv[])
{
    solve();
    return 0;
}

传送门
多套了一层年的循环,每年的本金记得要更新。

#include<iostream>
using namespace std;

const int N = 1e8 + 10;
int f[N];
int m, n, d;//m是最初的资产,n是年数,d是债卷的种类
struct bond
{
	int w, v;
}b[15];

void solve(){
	scanf("%d%d%d", &m, &n, &d);
	for (int i = 1; i <= d; ++i) scanf("%d%d", &b[i].w, &b[i].v);
	for (int i = 1; i <= n; ++i)
	{
		for (int k = 1; k <= d; ++k)//完全背包的开始
		{
			for (int j = b[k].w; j <= m; ++j)
			{
				f[j] = max(f[j], f[j - b[k].w] + b[k].v);
			}
		}
		m += f[m];//更新本金
	}
	printf("%d\n", m);
}

int main(int argc, char const *argv[])
{
	solve();
	return 0;
}

在这里插入图片描述
在这里插入图片描述
参考链接:
https://www.cnblogs.com/chuanlong/archive/2013/01/13/2858915.html

https://www.cnblogs.com/a1225234/p/5241668.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值