题目难度
中等
推荐理由
考验对背包问题的理解
题目知识点
分组背包,0/1背包
题意
农夫约翰有预算 ,有 台游戏机,每台游戏机价格为 。每台游戏机有个独立游戏,只有买了这台游戏机才能玩对应的游戏,每个游戏价格为 ,玩了之后奶牛产量增加 。问应该买哪些游戏机和游戏,使得奶牛产量最大,求最大产量。
其中,。
解题思路
第一反应:这不是 NOIP2006金明的预算方案 吗?那么这就是一个简单的分组背包问题!!!!
关于分组背包,我这里简要说说。分组背包是这样一种题:
有容量为 的背包,有 个组,每个组有 个组合,每个组合重量为 ,价值为 ,每个组只能选一个组合,求最大价值和。
其实每个组是独立的,所以我们每次单独考虑一个组。设 为背包容量为 的最大价值和,考虑每个组合选不选,我们有转移方程:
但是,这样子也许会带来一个问题:如果我们先枚举组合,再枚举容量,无法保证只选取了一个组合。
要保证只选取一个组合怎么办呢?我们先从大到小枚举容量 ,再枚举组合,就可以避免这个问题,因为 只会被没选过该组组合的转移而来,这个可能需要读者稍微思考一下。
那么回到这题,我们把每一个游戏机能形成的组合记录下来,每次跑一遍 0/1背包即可。
代码如下:
#include <bits/stdc++.h> #define N 100005 using namespace std; int f[N], cnt[55], w[55][1050], v[55][1050], p[11], q[11]; int main(){ int i, j, k, n, m, a, b, s1, s2; scanf("%d%d", &n, &m); for(i = 1; i <= n; i++){ scanf("%d%d", &a, &b); for(j = 0; j < b; j++) scanf("%d%d", &p[j], &q[j]); for(j = 0; j < (1 << b); j++){ s1 = s2 = 0; for(k = 0; k < b; k++){ if((1 << k) & j) s1 += p[k], s2 += q[k]; } if(a + s1 > m) continue; cnt[i]++; w[i][cnt[i]] = a + s1; v[i][cnt[i]] = s2; } } for(i = 1; i <= n; i++){ for(j = m; j >= 0; j--){ for(k = 1; k <= cnt[i]; k++){ if(j >= w[i][k]) f[j] = max(f[j], f[j - w[i][k]] + v[i][k]); } } } printf("%d", f[m]); return 0; }
然而,光荣TLE了!
这样子的复杂度是 的!一看数据范围,当场自闭!
数据范围提示我们,复杂度应该是 的。
那么怎么做呢?如果我们令 表示前 个游戏机,花费 元的最大产量。想一想怎么转移。
- 考虑买第个游戏机
由于游戏机是必买的,先不考虑买游戏,有 。
接下来考虑买游戏,每个游戏可买可不买,其实就是对第 维做一次 0/1 背包嘛,转移方程:。注意这里是对第 维做0/1背包,和第 维无关。
- 考虑不买第 个游戏机
这个很简单,就行了。
来到这里这题就做完了。
不过还有一个要注意的地方,在实现的时候这两部分是有顺序的,也就是必须先考虑买游戏机,再考虑不买游戏机。这个是为什么呢?其实是因为我们偷懒,直接把 的第 维拿来做 0/1 背包了。。如果用 单独表示第 维用了 元的最大产量,就不用管顺序了。
代码如下
#include <bits/stdc++.h> #define N 100005 using namespace std; int f[55][N]; int main(){ int i, j, k, n, m, a, b, w, v; scanf("%d%d", &n, &m); for(i = 1; i <= n; i++){ scanf("%d%d", &a, &b); for(j = m; j >= a; j--) f[i][j] = f[i - 1][j - a];//必须买游戏机 for(j = 1; j <= b; j++){ scanf("%d%d", &w, &v); for(k = m; k >= w + a; k--){//01背包,注意 k 是枚举到 w + a f[i][k] = max(f[i][k], f[i][k - w] + v); } } for(j = 0; j <= m; j++) f[i][j] = max(f[i][j], f[i - 1][j]);//不买游戏机 } printf("%d", f[n][m]); return 0; }