动态规划之背包问题初研究
2015-09-27 11:29
337 查看
//最近听从明神的建议,看了一点动态规划方面的内容,明显感觉智商不够用,所以我会尽可能将此段内容写成博客,并尽可能详细~~
01背包问题
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
因为每个物品只有一件,只有两种状态,放与不放,故称为0-1背包问题.
0-1背包问题的状态方程为
其中f[i][j]是前i个物品放入容量j的背包的最大价值,而如果不放入第i个物品,则是前i-1个物品占用j个背包的最大价值,而如果放入第i个物品则是前i-1个物品占用j-w[i](总容量-第i个物品的空间)+第i个物品的价值.
假设背包最大容量为10
1号物品价值为2,空间为4
2号物品价值为3,空间为5
3号物品价值为4,空间为6
那么当考虑1号背包时,空间为0,1,2,3,都装不下,直到4开始放入背包,f[1][4]=2;以后所有的j,f[i][j]都会取2,因为所有的f[0][j]都是0。当考虑2号物品时,f[2][0]=0,f[2][1]=0,f[2][2],f[2][3]都不变,直到f[2][4]=2,而f[2][5]时变为3(其实我们想想,假设背包容量就是5,那么自然最大价值就是2号物品的价值),之后直到f[2][9]时f[2][9]=max(f[1][9],f[1][4]+a[2])则修改为5,接下去考虑3号物品,f[3][0]=0,f[3][1]=0,f[3][2]=0,f[3][3]=0,f[3][4]=2,f[3][5]=3,f[3][6]=4,f[3][7]=4,f[3][8]=4,f[3][9]=5,f[3][10]=max(f[2][10],f[2][4]+4},即f[3][10]=6,成功了~~
如果参看网上的许多代码,会发现他们是从0开始循环的,这样f[i-1][j]就可能会为负数,但是一般f[-1][j]也为0,所以并没有问题。
用一维数组来表达很重要,因为f这个二维数组是和n,m的大小直接挂钩的,至少为两者的平方,所以空间占据很大,有些题目会控制空间,所以需要理解用一维数组来解决01背包问题~~
一维数组的伪代码如下:
for i=1..N
for
v=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
注意一定内层循环一定要逆序。我们知道f[v]是由两个状态得来的,f[i-1][v]和f[i-1][v-c[i]],使用一维数组时,当第i次循环之前时,f[v]实际上就是f[i-1][v],所以只需考虑f[v]和f[v-c[i]]+w[i]哪个更大,至于逆序是因为假设背包容量为5,而有一个第2号物品价值特别大,则考虑4号物品时,f[v]=max{f[v],f[v-c[i]+w[i]}的结果是再将2号装了一次,这就会导致2号物品被装了两次,而每个物品只有一个,所以不符合题意。但是这却是完全背包的解法,因为完全背包是允许一个物品多次入包的。
练习题:http://poj.org/problem?id=3624
此题一开始是用二维数组做的,不幸空间溢出~~
接下来是完全背包问题,即每件物品可以取出任意件数
假设取出的件数为k
则他的状态转移方程为f[i][v]=max{f[i-1][v-k*c[i]+k*w[i]};(0<=k*c[i]<=v)
最好的方法是用一维数组
先看伪代码:
for i=1..N
for
v=0..V
f[v]=max{f[v],f[v-cost]+weight}
刚才介绍说如果内层循环正序,那么就可能出现一个物品被多次取出的问题,所以被排除,但是完全背包问题只要空间足够就可以不停地使用一个物品,比如2号物品重量为2,背包容量为10,只要2号背包的价值足够大,完全可能出现2号物品多次入包的情况.
练习题:http://poj.org/problem?id=1384
未完待续.....
01背包问题
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
因为每个物品只有一件,只有两种状态,放与不放,故称为0-1背包问题.
0-1背包问题的状态方程为
f[i][j]=max{f[i-1][j],f[i-1][j-w[i]]+a[i]};
其中f[i][j]是前i个物品放入容量j的背包的最大价值,而如果不放入第i个物品,则是前i-1个物品占用j个背包的最大价值,而如果放入第i个物品则是前i-1个物品占用j-w[i](总容量-第i个物品的空间)+第i个物品的价值.
#include <stdio.h> #include <memory.h> int max(int a,int b) { return a>b?a:b; } int main() { int a[1000]; int f[100][100]; int w[1000]; int i,j,n,m; printf("请输入物品的数量\n"); scanf("%d",&n); printf("请依次输入物品的价值\n"); for (i=1;i<=n;i++) scanf("%d",&a[i]); printf("请输入背包的最大容量\n"); scanf("%d",&m); printf("请依次输入物品的所占空间\n"); for (i=1;i<=n;i++) scanf("%d",&w[i]); //此时的目的是将n个物品放入空间为m的背包里,第i个背包价值为a[i],所占空间为w[i],接下来求最大价值 memset(f,0,sizeof(f)); for(i=1;i<=n;i++) for (j=0;j<=m;j++) if (j>=w[i])//这里与状态方程稍有出入,因为如果<span style="font-family:宋体;">现在我提供的空间的都不够放入i物品,那么</span>肯定不放入,直接就是f[i-1][j](<span style="font-family:宋体;">主要是怕j-w[i]下标越界)</span> f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+a[i]); else f[i][j]=f[i-1][j]; printf("最大价值为:%d",f [m]); return 0; }我觉得这个式子如何计算也有必要讲解一下:
假设背包最大容量为10
1号物品价值为2,空间为4
2号物品价值为3,空间为5
3号物品价值为4,空间为6
那么当考虑1号背包时,空间为0,1,2,3,都装不下,直到4开始放入背包,f[1][4]=2;以后所有的j,f[i][j]都会取2,因为所有的f[0][j]都是0。当考虑2号物品时,f[2][0]=0,f[2][1]=0,f[2][2],f[2][3]都不变,直到f[2][4]=2,而f[2][5]时变为3(其实我们想想,假设背包容量就是5,那么自然最大价值就是2号物品的价值),之后直到f[2][9]时f[2][9]=max(f[1][9],f[1][4]+a[2])则修改为5,接下去考虑3号物品,f[3][0]=0,f[3][1]=0,f[3][2]=0,f[3][3]=0,f[3][4]=2,f[3][5]=3,f[3][6]=4,f[3][7]=4,f[3][8]=4,f[3][9]=5,f[3][10]=max(f[2][10],f[2][4]+4},即f[3][10]=6,成功了~~
如果参看网上的许多代码,会发现他们是从0开始循环的,这样f[i-1][j]就可能会为负数,但是一般f[-1][j]也为0,所以并没有问题。
用一维数组来表达很重要,因为f这个二维数组是和n,m的大小直接挂钩的,至少为两者的平方,所以空间占据很大,有些题目会控制空间,所以需要理解用一维数组来解决01背包问题~~
一维数组的伪代码如下:
for i=1..N
for
v=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
注意一定内层循环一定要逆序。我们知道f[v]是由两个状态得来的,f[i-1][v]和f[i-1][v-c[i]],使用一维数组时,当第i次循环之前时,f[v]实际上就是f[i-1][v],所以只需考虑f[v]和f[v-c[i]]+w[i]哪个更大,至于逆序是因为假设背包容量为5,而有一个第2号物品价值特别大,则考虑4号物品时,f[v]=max{f[v],f[v-c[i]+w[i]}的结果是再将2号装了一次,这就会导致2号物品被装了两次,而每个物品只有一个,所以不符合题意。但是这却是完全背包的解法,因为完全背包是允许一个物品多次入包的。
练习题:http://poj.org/problem?id=3624
此题一开始是用二维数组做的,不幸空间溢出~~
#include<stdio.h> #include <string.h> int max(int a,int b) { return a>b?a:b; } int main() { int p[3403][3403]; int w[4005];//重量 int d[4005];//价值 int n,m,i,j; scanf("%d%d",&n,&m); for (i=1;i<=n;i++) scanf("%d%d",&w[i],&d[i]); memset(p,0,sizeof(p)); for (i=1;i<=n;i++)//物品从1号到n号 for (j=0;j<=m;j++)//最大重量从0到m if (j>=w[i])//如果空间够放w[i] p[i][j]=max(p[i-1][j],p[i-1][j-w[i]]+d[i]); else p[i][j]=p[i-1][j]; printf("%d\n",p [m]); return 0; } 改用一维数组立马 AC #include <stdio.h> #include <string.h> int max(int a,int b) { return a>b?a:b; } int main() { int f[14000]; int i,j,n,m; int w[4005],d[4005]; scanf("%d%d",&n,&m); for (i=1;i<=n;i++) scanf("%d%d",&w[i],&d[i]); memset(f,0,sizeof(f)); for (i=1;i<=n;i++) for (j=m;j>=w[i];j--) f[j]=max(f[j],f[j-w[i]]+d[i]); printf("%d\n",f[m]); return 0; }
接下来是完全背包问题,即每件物品可以取出任意件数
假设取出的件数为k
则他的状态转移方程为f[i][v]=max{f[i-1][v-k*c[i]+k*w[i]};(0<=k*c[i]<=v)
最好的方法是用一维数组
先看伪代码:
for i=1..N
for
v=0..V
f[v]=max{f[v],f[v-cost]+weight}
刚才介绍说如果内层循环正序,那么就可能出现一个物品被多次取出的问题,所以被排除,但是完全背包问题只要空间足够就可以不停地使用一个物品,比如2号物品重量为2,背包容量为10,只要2号背包的价值足够大,完全可能出现2号物品多次入包的情况.
练习题:http://poj.org/problem?id=1384
#include <stdio.h>//此题代码思路基本如上所述, #include <string.h> int min(int a,int b) { return a<b?a:b; } int main() { int T,e,f,i,j,n,k,empty,full; int w[600];//重量 int v[600];//价值 int p[10100]; scanf("%d",&T); while(T--) { memset(v,0,sizeof(v)); memset(w,0,sizeof(w)); scanf("%d%d",&empty,&full); full=full-empty; p[0]=0; for (i=1;i<=full;i++) p[i]=10000000; scanf("%d",&n); for (k=1;k<=n;k++) { scanf("%d%d",&v[k],&w[k]);//价值数量 } for (i=1;i<=n;i++) for (j=w[i];j<=full;j++) p[j]=min(p[j],p[j-w[i]]+v[i]); if (p[full]==10000000) printf("This is impossible.\n"); else printf("The minimum amount of money in the piggy-bank is %d.\n",p[full]); } return 0; }
未完待续.....
相关文章推荐
- 3.3.5 使用HtmlDiff对象
- KissXML解析
- 在C/C++中调用python的简单笔记part 1
- 对实训以及实训后的看法
- Nginx 日志
- 消息摘要算法HmacMD5的实现
- noip2010 机器翻译 (模拟)
- 我也来写写如何在百度收录列表显示缩略图
- 导出csv文件
- BIND的主从复制及子域DNS的授权(二)
- c++如何返回数组
- 2015年终总结(上)
- cygwin主要命令
- 【软工文档】 之 文档总结
- iOS开发:GET与POST接口网络请求以及对AFNetworking的二次封装
- 中秋啦
- RDO部署多节点OpenStack Havana(OVS+GRE)
- 吞噬算法
- [BUG] CS0234: 命名空间“System.Web.Mvc”中不存在类型或命名空间名称“Ajax”(是否缺少程序集引用?)
- JS实现横向与竖向两个选项卡Tab联动的方法