HDOJ 1114 Piggy-Bank 【动态规划 完全背包】
2015-10-13 00:51
288 查看
题目链接:
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
是一种恰好装满背包的最优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将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的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:
o(vn)的算法:
这个算法使用一维数组,先看伪代码:
你会发现,这个伪代码与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-1][
所以,
将这个方程用一维数组实现,便得到了上面的伪代码。
最后抽象出处理一件完全背包类物品的过程伪代码:
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
是一种恰好装满背包的最优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将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]}
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int INF = 0x3f3f3f3f; const int MAXL = 500+5; int t, e, f, v, n, value[MAXL], weight[MAXL], dp[10005]; int min(int a, int b) { return a < b ? a : b; } int main(){ scanf("%d", &t); while(t--){ scanf("%d%d", &e, &f); v = f-e; scanf("%d", &n); //---初始化 dp[0] = 0; for(int i = 1; i <= v; i++) dp[i] = INF; //--- for(int i = 0; i < n; i++) scanf("%d%d", &value[i], &weight[i]); for(int i = 0; i < n; i++){ for(int j = weight[i]; j <= v; j++){ dp[j] = min(dp[j-weight[i]]+value[i], dp[j]); } } if(dp[v] == INF) printf("This is impossible.\n"); else printf("The minimum amount of money in the piggy-bank is %d.\n", dp[v]); } return 0; }
相关文章推荐
- Codeforces #325 D. Phillip and Trains (bfs)
- 随我一起慢慢揭开Andoird里事件分发机制的神秘面纱
- Tornado模板转义处理
- gitlab安装过程总结
- ARM assembler in Raspberry Pi – Chapter 1
- Android 应用程序组件
- 程序员修炼之路
- nginx rewrite 参数和例子
- Codeforces #325 C. Gennady the Dentist
- 关于开发工具的一些建议
- java利用正则表达式验证邮箱
- 修改ubuntu 的命令提示符("修改主机名和用户名")
- 用着色器画Sierpinski
- 观察者模式
- Objective-C编程语言概叙
- Oracle视图、序列、索引、约束
- OpenGL用着色器画绿色三角形
- Java字节码指令列表
- CF div2 325 C
- 冒泡排序算法小试[笑哭]