题目描述
有一个存钱罐,存储一些硬币。已知存钱罐的重量、存储了硬币后的存钱罐的重量以及每种硬币的重量和面值。求存钱罐中硬币的面值之和可能的最小值,或者如果无论如何放置硬币都不能达到存钱罐的重量要求,则输出“This is impossible.”
输入格式
第一行输入T表示有T组数据。每组数据首先一行输入E和F表示存钱罐没装硬币时和装了硬币后的重量。下一行输入N表示有N种硬币。接下来N行,每行输入两个整数P和W表示硬币的面值和重量。并且1<=N<=500,1<=P<=50000,1<=W<=10000,1<=E<=F<=10000
输出格式
对于每组数据输出一行,一行“The minimum amount of money in the piggy-bank is ans”,ans为该数据的解。或者解不存在输出“This is impossible”。
Sample Input
3
10 110
2
1 1
30 50
10 110
2
1 1
50 30
1 6
2
10 3
20 4
Sample Output
The minimum amount of money in the piggy-bank is 60.
The minimum amount of money in the piggy-bank is 100.
This is impossible.
题目分析
这道题是完全背包模板题。接下来先分析一下完全背包问题。
简单01背包中是从N个物品里选,每个物品只能用1次,完全背包则不同,每个物品可以用无限次。
所以01背包里每个物品只能是选(1)或者不选(0)两种状态,而完全背包里,假设背包体积为V,对于某个体积为v的物品可以有k+1种状态,其中满足k*v<=V,对应着选该物品0、1、2……k次。
01背包:
如果物品能放入背包,则动态转移方程为 dp[i][j]=max( dp[i-1][j],dp[i-1][ j-v[i] ]+ w[i] ), j>=v[i]
如果物品不能放入背包,dp[i][j]=dp[i-1][j] ,j<v[i]
完全背包:
动态转移方程满足:dp[i][j]=max{dp[i-1][ j-k×v[i] ] + k×w[i]},其中 0 ≤ k×v[i] ≤ j
可以发现,当k只能取0、1时的特例就是简单的0-1背包问题。
可以发现,选0次,即不选是肯定存在的,dp[i][j]=dp[i-1][j];
然后选1次,那就不一定存在了,可能可以选,也可能不可以选,假设可以选,在选0次该物品求出dp[i][j]的基础上,确定是否选该物品:if (j>=v[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
选2次、选3次,依次类推,一直到选k次。
上面这种也就是简单01背包中二维数组实现的写法,即手动模拟for循环,循环两次,不过完全背包中是循环k次,(k>1),但是对于只有两种选择的01背包,也可以用if、else判断,见滚动数组实现。
二维数组实现
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1010, maxv = 1010;
int v[maxn], w[maxn];
int dp[maxn][maxv];//N行V列,0行0列初始为0
int main() {
int N, V; //N为物品种类数,V为背包总容量
cin >> N >> V;
for (int i = 1; i <= N; i++) cin >> v[i] >> w[i];
for (int i = 1; i <= N; i++)
for (int j = 0; j <= V; j++) //"已经初始化第0列为0了,所以不管j从1还是从0开始,都一样"
for (int k = 0; k * v[i] <= j; k++)
dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * w[i]);
cout << dp[N][V] << endl;
return 0;
}
上面第二重循环,j从0开始时,由于物品体积都大于0,继续循环的条件k*v[i]<=j,所以当j=0时,只会循环一次,即不选择物品时,dp[i][0]=dp[i-1][0],最终等于dp[0][0]=0,所以当初始化第0列以后,j从1开始就行。
二重循环里的语句三重循环的循环k次可以类比简单01背包的二重循环里面的语句,01背包是循环两次的简便写法,完全背包则是循环k次,不过为了dp[i][j]第一次能够取到dp[i-1][j],应该给dp[i][j]先初始化一个小于等于0的值,因为最后得到的dp二维表里,值最小值就是0。
时间复杂度优化
上面要经过三重循环,时间复杂度较高,我们可以将 f[i, j] = max { f[i-1, j-k*v[i]] + k*w[i] } 展开,就可以发现如下规律:
假设第i个物品体积为v,价值为w,最多能将k个该物品装进体积为j的背包,即k*v<=j
对于选择前i个物品,体积为j的背包:最多可以装入k个该物品,
f[i,j]=max(f[i−1][j],f[i−1][j−v]+w,f[i−1][j−2v]+2w……f[i−1][j−kv]+kw)
对于选择前i个物品,体积为j-v的背包:最多可以装入k-1个该物品,
二者进行等量代换,可以得到优化后的完全背包的状态转移方程f[i][j]=max(f[i-1][j],f[i][j-v]+w)
对比简单01背包的状态转移方程:f[i][j]=max(f[i-1][j],f[i-1][j-v]+w)
#include <iostream>
using namespace std;
const int maxn = 1010, maxv = 1010;
int v[maxn], w[maxn];
int dp[maxn][maxv];//N行V列,0行0列初始为0
int main() {
int N, V;
cin >> N >> V;
for (int i = 1; i <= N; i++) cin >> v[i] >> w[i];
for (int i = 1; i <= N; i++)
for (int j = 0; j <= V; j++) {
dp[i][j] = dp[i - 1][j];
if (j >= v[i]) dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);
}
cout << dp[N][V] << endl;
return 0;
}
仅有一处不同,可以看出简单01背包的第i行的dp[i][j]完全取决于第i-1行的dp[i-1][0]到dp[i-1][j]的数据,
而完全背包第i行的dp[i][j]则取决于第i-1行的一个数据dp[i-1][j]以及第i行本身的dp[i][0]到dp[i][j-1]的数据。
优化+一维数组实现
完全背包中第i行前几列更新的数据需要被后几列用到,所以像简单背包那样进行一维数组优化的时候,二重循环就不能倒序循环了,要正序循环了。
可以理解为给体积为j的背包中选取k个某物品也是一个递推的过程,选取k个该物品就去选取k-1个该物品的时候找答案,一直往下找。
而01背包中若是也正序更新,第i-1行的数据就会被第i行新更新的数据覆盖,但后面用到的是第i-1行的数据,为了避免数据被破坏,所以倒序更新。
#include <iostream>
using namespace std;
const int maxn = 1010, maxv = 1010;
int v[maxn], w[maxn];
int dp[maxv];
int main() {
int N, V;
cin >> N >> V;
for (int i = 1; i <= N; i++) cin >> v[i] >> w[i];
for (int i = 1; i <= N; i++)
for (int j = v[i]; j <= V; j++)
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
cout<<dp[V]<<endl;
return 0;
}
本题Code:
#include<bits/stdc++.h>
using namespace std;
int dp[10005];
int main()
{
int n, t, e, f, cost, value, v;
cin >> t;
while (t--)
{
cin >> e >> f;
v = f - e;
cin >> n;
memset(dp, -1, sizeof(dp)); //初始化状态为-1,-1表示状态未到达
dp[0] = 0;
for (int i = 0; i < n; i++) {
cin >> value >> cost; //读取每种硬币的价值和重量
for (int j = 0; j + cost <= v; j++) {
if (dp[j] == -1) continue; //状态未到达,不能进行转移
else if (dp[j + cost] == -1) { //如果第一次到达该状态,直接赋值
dp[j + cost] = dp[j] + value;
}
else {
dp[j + cost] = min(dp[j + cost], dp[j] + value);
}
}
}
if (dp[v] == -1) { //dp[v]为-1说明所有硬币任意组合都不能得到v的重量
cout << "This is impossible." << endl;
}
else {
cout << "The minimum amount of money in the piggy-bank is " << dp[v] << "." << endl;
}
}
return 0;
}
本文完全背包问题的分析大多来源于
原文链接:https://blog.csdn.net/HangHug_L/article/details/114238728