HDOJ 1114 Piggy-Bank 【动态规划 完全背包】
题目链接:
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=17679
题目描述:
一个原重量为e,装满重量为f的小猪存钱罐,有n种硬币,每种货币有value和weight,每种硬币可以无限次选用
要求输出使重量符合要求的,价值最小的组合方式,
如果没有符合重量要求的硬币组合,输出impossible
问题模型:
完全背包,只不过因为要求价值最小,所以将状态方程的max换成min
初始化的细节问题:
我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。
一种区别这两种问法的实现方法是在初始化的时候有所不同。
如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1..V]均设为-∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0..V]全部设为0。
为什么呢?可以这样理解:
初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。
如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。
如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。
完全背包:
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
这个问题非常类似于01背包问题,所不同的是每种物品有无限件。
也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。
如果仍然按照解 01背包时的思路,令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}
o(vn)的算法:
这个算法使用一维数组,先看伪代码:
for i=1..N
for v=0..V
f[v]=max{f[v],f[v-cost]+weight}
你会发现,这个伪代码与P01的伪代码只有v的循环次序不同而已。
为什么这样一改就可行呢?
首先想想为什么P01中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1] [v-c[i]]递推而来。
换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。
而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v=0..V的顺序循环。这就是这个简单的程序为何成立的道理。
这个算法也可以以另外的思路得出。
将基本思路中求解f[i][v-c[i]]的状态转移方程显式地写出来,代入原方程中:
f[i][v]=f[i-1][v-k*c[i]]+k*w[i]
f[i][v-
c[i]
]=f[i-1][v-c[i]-(k-1)*c[i]]+(k-1)*w[i]
=f[i-1][v-k*c[i]
]+k*w[i]
- w[i]
所以,
f[i][v]=f[i][v-c[i]]+w[i]
将这个方程用一维数组实现,便得到了上面的伪代码。
最后抽象出处理一件完全背包类物品的过程伪代码:
procedure CompletePack(cost,weight)
for v=cost..V
f[v]=max{f[v],f[v-c[i]]+w[i]}
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 const int INF = 0x3f3f3f3f; 6 const int MAXL = 500+5; 7 int t, e, f, v, n, value[MAXL], weight[MAXL], dp[10005]; 8 int min(int a, int b) { return a < b ? a : b; } 9 10 int main(){ 11 scanf("%d", &t); 12 while(t--){ 13 scanf("%d%d", &e, &f); 14 v = f-e; 15 scanf("%d", &n); 16 //---初始化 17 dp[0] = 0; 18 for(int i = 1; i <= v; i++) dp[i] = INF; 19 //--- 20 for(int i = 0; i < n; i++) scanf("%d%d", &value[i], &weight[i]); 21 for(int i = 0; i < n; i++){ 22 for(int j = weight[i]; j <= v; j++){ 23 dp[j] = min(dp[j-weight[i]]+value[i], dp[j]); 24 } 25 } 26 if(dp[v] == INF) printf("This is impossible.\n"); 27 else printf("The minimum amount of money in the piggy-bank is %d.\n", dp[v]); 28 } 29 30 return 0; 31 }